Compare commits

..

3 Commits

Author SHA1 Message Date
Romain Vimont
61e73b7b63 Apply resizing workaround on size changed
The "resized" event is called on user resizing only, while "size
changed" is called on any resize, including at every step of a
progressive resize animation.
2020-05-11 03:39:20 +02:00
Romain Vimont
5edd5a2617 Detect fullscreen changes
The window state may be changed by the window manager without scrcpy
being aware of it (for example the fullscreen mode on macOS).

Check on every "size changed" event whether the fullscreen mode has
changed.
2020-05-11 03:37:16 +02:00
Romain Vimont
9006f545fc Simplify size changes in fullscreen or maximized
If the content size changes (due to rotation for example) while the
window is maximized or fullscreen, the resize must be applied once
fullscreen and maximized are disabled.

The previous strategy consisted in storing the windowed size, computing
the target size on rotation, and applying it window restoration. But
tracking the windowed size (while ignoring the non-windowed size) was
tricky, due to unspecified order of SDL events (e.g. size changes can be
notified before "maximized" events), race conditions when reading window
flags, different behaviors on different platforms...

To simplify the whole resize management, store the old content size (the
frame size, possibly rotated) when it changes while the window is
maximized or fullscreen, so that the new optimal size can be computed on
window restoration.
2020-05-11 03:36:29 +02:00
4 changed files with 124 additions and 173 deletions

View File

@@ -7,6 +7,33 @@
#include "util/lock.h" #include "util/lock.h"
#include "util/log.h" #include "util/log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
@@ -460,17 +487,11 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// SDL touch event coordinates are normalized in the range [0; 1] // SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww; float x = from->x * screen->content_size.width;
int32_t y = from->y * wh; float y = from->y * screen->content_size.height;
to->inject_touch_event.position.point = to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y); screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0; to->inject_touch_event.buttons = 0;
return true; return true;
@@ -487,6 +508,13 @@ input_manager_process_touch(struct input_manager *im,
} }
} }
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->content_size.width ||
y < 0 || y >= im->screen->content_size.height;
}
static bool static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) { struct control_msg *to) {
@@ -524,15 +552,10 @@ input_manager_process_mouse_button(struct input_manager *im,
action_home(im->controller, ACTION_DOWN | ACTION_UP); action_home(im->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x; bool outside =
int32_t y = event->y; is_outside_device_screen(im, event->x, event->y);
screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) { if (outside) {
screen_resize_to_fit(im->screen); screen_resize_to_fit(im->screen);
return; return;
@@ -556,15 +579,9 @@ input_manager_process_mouse_button(struct input_manager *im,
static bool static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) { struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = { struct position position = {
.screen_size = screen->frame_size, .screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y), .point = get_mouse_point(screen),
}; };
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@@ -133,10 +133,10 @@ static int
event_watcher(void *data, SDL_Event *event) { event_watcher(void *data, SDL_Event *event) {
(void) data; (void) data;
if (event->type == SDL_WINDOWEVENT if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) { && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
// In practice, it seems to always be called from the same thread in // In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround. // that specific case. Anyway, it's just a workaround.
screen_render(&screen, true); screen_render(&screen);
} }
return 0; return 0;
} }

View File

@@ -68,16 +68,6 @@ get_preferred_display_bounds(struct size *bounds) {
return true; return true;
} }
static bool
is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// return the optimal size of the window, with the following constraints: // return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it // - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders) // crops the black borders)
@@ -90,36 +80,40 @@ get_optimal_size(struct size current_size, struct size content_size) {
return current_size; return current_size;
} }
struct size window_size;
struct size display_size; struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size // could not get display bounds, do not constraint the size
window_size.width = current_size.width; w = current_size.width;
window_size.height = current_size.height; h = current_size.height;
} else { } else {
window_size.width = MIN(current_size.width, display_size.width); w = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
if (is_optimal_size(window_size, content_size)) { if (h == w * content_size.height / content_size.width
return window_size; || w == h * content_size.width / content_size.height) {
// The size is already optimal, if we ignore rounding errors due to
// integer window dimensions
return (struct size) {w, h};
} }
bool keep_width = content_size.width * window_size.height bool keep_width = content_size.width * h > content_size.height * w;
> content_size.height * window_size.width;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
window_size.height = content_size.height * window_size.width h = content_size.height * w / content_size.width;
/ content_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already // remove black borders on left and right (or none at all if it already
// fits) // fits)
window_size.width = content_size.width * window_size.height w = content_size.width * h / content_size.height;
/ content_size.height;
} }
return window_size; // w and h must fit into 16 bits
assert(w < 0x10000 && h < 0x10000);
return (struct size) {w, h};
} }
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
@@ -156,51 +150,6 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width,
return window_size; return window_size;
} }
static void
screen_update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
int ww, wh;
SDL_GetWindowSize(screen->window, &ww, &wh);
struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh};
LOGI("update_content_rect: window=%dx%d, drawable=%dx%d content=%ux%u", ww, wh, dw, dh, content_size.width, content_size.height);
SDL_Rect *rect = &screen->rect;
if (is_optimal_size(drawable_size, content_size)) {
rect->x = 0;
rect->y = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.height;
LOGI(" --> (optimal) rect (%d, %d) %dx%d", rect->x, rect->y, rect->w, rect->h);
return;
}
bool keep_width = content_size.width * drawable_size.height
> content_size.height * drawable_size.width;
if (keep_width) {
rect->x = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.width * content_size.height
/ content_size.width;
rect->y = (drawable_size.height - rect->h) / 2;
} else {
rect->y = 0;
rect->h = drawable_size.height;
rect->w = drawable_size.height * content_size.width
/ content_size.height;
rect->x = (drawable_size.width - rect->w) / 2;
}
LOGI(" --> rect (%d, %d) %dx%d", rect->x, rect->y, rect->w, rect->h);
}
void void
screen_init(struct screen *screen) { screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
@@ -290,6 +239,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
const char *renderer_name = r ? NULL : renderer_info.name; const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
// starts with "opengl" // starts with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) { if (screen->use_opengl) {
@@ -333,10 +289,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen);
return true; return true;
} }
@@ -407,12 +359,18 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
struct size new_content_size = struct size new_content_size =
get_rotated_size(screen->frame_size, rotation); get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size); if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return;
}
set_content_size(screen, new_content_size);
screen->rotation = rotation; screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation); LOGI("Display rotation set to %u", rotation);
screen_render(screen, true); screen_render(screen);
} }
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
@@ -420,16 +378,20 @@ static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size); set_content_size(screen, new_content_size);
screen->frame_size = new_frame_size;
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
@@ -471,19 +433,15 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex); mutex_unlock(vb->mutex);
screen_render(screen, false); screen_render(screen);
return true; return true;
} }
void void
screen_render(struct screen *screen, bool update_content_rect) { screen_render(struct screen *screen) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) { if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
} else { } else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is // rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation) // counterclockwise (to be consistent with --lock-video-orientation)
@@ -493,14 +451,12 @@ screen_render(struct screen *screen, bool update_content_rect) {
SDL_Rect *dstrect = NULL; SDL_Rect *dstrect = NULL;
SDL_Rect rect; SDL_Rect rect;
if (screen->rotation & 1) { if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2; struct size size = screen->content_size;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2; rect.x = (size.width - size.height) / 2;
rect.w = screen->rect.h; rect.y = (size.height - size.width) / 2;
rect.h = screen->rect.w; rect.w = size.height;
rect.h = size.width;
dstrect = &rect; dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
} }
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect, SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
@@ -523,15 +479,20 @@ screen_switch_fullscreen(struct screen *screen) {
} }
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed"); LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen, true); screen_render(screen);
} }
void void
screen_resize_to_fit(struct screen *screen) { screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen || screen->maximized) { if (screen->fullscreen) {
return; return;
} }
if (screen->maximized) {
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct size optimal_size = struct size optimal_size =
get_optimal_window_size(screen, screen->content_size); get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
@@ -556,31 +517,41 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height); content_size.height);
} }
static inline bool
is_fullscreen(const struct screen *screen) {
uint32_t flags = SDL_GetWindowFlags(screen->window);
return !!(flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP));
}
static void
update_fullscreen_state(struct screen *screen) {
// There is no SDL event to detect fullscreen changes, so store the
// state in a field and compare on every "size changed" event
bool fullscreen = is_fullscreen(screen);
if (fullscreen != screen->fullscreen) {
// Fullscreen state have changed
screen->fullscreen = fullscreen;
if (!fullscreen && !screen->maximized) {
apply_pending_resize(screen);
}
}
}
void void
screen_handle_window_event(struct screen *screen, screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) { const SDL_WindowEvent *event) {
switch (event->event) { switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
LOGI("EXPOSED"); screen_render(screen);
screen_render(screen, true);
break; break;
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
LOGI("SIZE_CHANGED %dx%d", event->data1, event->data2); update_fullscreen_state(screen);
screen_render(screen, true); screen_render(screen);
break; break;
case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_MAXIMIZED:
LOGI("MAXIMIZED");
screen->maximized = true; screen->maximized = true;
break; break;
case SDL_WINDOWEVENT_RESTORED: case SDL_WINDOWEVENT_RESTORED:
LOGI("RESTORED");
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
}
screen->maximized = false; screen->maximized = false;
apply_pending_resize(screen); apply_pending_resize(screen);
break; break;
@@ -594,13 +565,6 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
int32_t w = screen->content_size.width; int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height; int32_t h = screen->content_size.height;
screen_hidpi_scale_coords(screen, &x, &y);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct point result; struct point result;
switch (rotation) { switch (rotation) {
case 0: case 0:
@@ -623,15 +587,3 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
} }
return result; return result;
} }
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// scale for HiDPI (64 bits for intermediate multiplications)
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}

View File

@@ -29,8 +29,6 @@ struct screen {
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation; unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
@@ -58,12 +56,6 @@ struct screen {
.height = 0, \ .height = 0, \
}, \ }, \
.rotation = 0, \ .rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \ .has_frame = false, \
.fullscreen = false, \ .fullscreen = false, \
.maximized = false, \ .maximized = false, \
@@ -97,11 +89,8 @@ bool
screen_update_frame(struct screen *screen, struct video_buffer *vb); screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void void
screen_render(struct screen *screen, bool update_content_rect); screen_render(struct screen *screen);
// switch the fullscreen mode // switch the fullscreen mode
void void
@@ -128,11 +117,4 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
struct point struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y); screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
#endif #endif