2018-02-08 18:14:13 +08:00
|
|
|
#include "screen.h"
|
|
|
|
|
2019-11-28 04:11:40 +08:00
|
|
|
#include <assert.h>
|
2018-02-08 18:14:13 +08:00
|
|
|
#include <string.h>
|
2019-03-03 06:52:22 +08:00
|
|
|
#include <SDL2/SDL.h>
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
#include "events.h"
|
2021-10-23 00:51:20 +08:00
|
|
|
#include "icon.h"
|
2021-10-28 00:43:47 +08:00
|
|
|
#include "options.h"
|
2019-03-02 22:16:55 +08:00
|
|
|
#include "video_buffer.h"
|
2019-11-24 18:53:00 +08:00
|
|
|
#include "util/log.h"
|
2018-02-08 18:14:13 +08:00
|
|
|
|
|
|
|
#define DISPLAY_MARGINS 96
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
|
2021-04-11 21:01:05 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
static inline struct sc_size
|
|
|
|
get_rotated_size(struct sc_size size, int rotation) {
|
|
|
|
struct sc_size rotated_size;
|
2020-04-08 05:03:23 +08:00
|
|
|
if (rotation & 1) {
|
|
|
|
rotated_size.width = size.height;
|
|
|
|
rotated_size.height = size.width;
|
|
|
|
} else {
|
|
|
|
rotated_size.width = size.width;
|
|
|
|
rotated_size.height = size.height;
|
|
|
|
}
|
|
|
|
return rotated_size;
|
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
// get the window size in a struct sc_size
|
|
|
|
static struct sc_size
|
2022-01-15 05:17:30 +08:00
|
|
|
get_window_size(const struct sc_screen *screen) {
|
2018-02-08 18:14:13 +08:00
|
|
|
int width;
|
|
|
|
int height;
|
2020-05-11 09:11:13 +08:00
|
|
|
SDL_GetWindowSize(screen->window, &width, &height);
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size size;
|
2018-02-08 18:14:13 +08:00
|
|
|
size.width = width;
|
|
|
|
size.height = height;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
static struct sc_point
|
2022-01-15 05:17:30 +08:00
|
|
|
get_window_position(const struct sc_screen *screen) {
|
2021-06-15 03:24:51 +08:00
|
|
|
int x;
|
|
|
|
int y;
|
|
|
|
SDL_GetWindowPosition(screen->window, &x, &y);
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_point point;
|
2021-06-15 03:24:51 +08:00
|
|
|
point.x = x;
|
|
|
|
point.y = y;
|
|
|
|
return point;
|
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// set the window size to be applied when fullscreen is disabled
|
2019-03-03 03:09:56 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
2020-05-11 09:11:13 +08:00
|
|
|
assert(!screen->fullscreen);
|
|
|
|
assert(!screen->maximized);
|
|
|
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
2019-03-03 06:52:22 +08:00
|
|
|
static bool
|
2021-10-30 21:20:39 +08:00
|
|
|
get_preferred_display_bounds(struct sc_size *bounds) {
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_Rect rect;
|
2021-12-07 06:46:01 +08:00
|
|
|
if (SDL_GetDisplayUsableBounds(0, &rect)) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
|
|
|
|
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
static bool
|
2021-10-30 21:20:39 +08:00
|
|
|
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
2020-04-25 06:19:44 +08:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// return the optimal size of the window, with the following constraints:
|
2019-03-03 03:09:56 +08:00
|
|
|
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
|
|
|
// crops the black borders)
|
2018-02-08 18:14:13 +08:00
|
|
|
// - it keeps the aspect ratio
|
|
|
|
// - it scales down to make it fit in the display_size
|
2021-10-30 21:20:39 +08:00
|
|
|
static struct sc_size
|
2021-12-21 02:17:15 +08:00
|
|
|
get_optimal_size(struct sc_size current_size, struct sc_size content_size,
|
|
|
|
bool within_display_bounds) {
|
2020-04-08 05:03:23 +08:00
|
|
|
if (content_size.width == 0 || content_size.height == 0) {
|
2018-02-16 06:50:38 +08:00
|
|
|
// avoid division by 0
|
|
|
|
return current_size;
|
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size display_size;
|
2021-12-21 02:17:15 +08:00
|
|
|
if (!within_display_bounds ||
|
|
|
|
!get_preferred_display_bounds(&display_size)) {
|
|
|
|
// do not constraint the size
|
2021-12-21 02:21:19 +08:00
|
|
|
window_size = current_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
} else {
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.width = MIN(current_size.width, display_size.width);
|
|
|
|
window_size.height = MIN(current_size.height, display_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
if (is_optimal_size(window_size, content_size)) {
|
|
|
|
return window_size;
|
2020-04-25 04:52:02 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
bool keep_width = content_size.width * window_size.height
|
|
|
|
> content_size.height * window_size.width;
|
2018-02-08 18:14:13 +08:00
|
|
|
if (keep_width) {
|
|
|
|
// remove black borders on top and bottom
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.height = content_size.height * window_size.width
|
|
|
|
/ content_size.width;
|
2018-02-08 18:14:13 +08:00
|
|
|
} else {
|
2019-03-03 03:09:56 +08:00
|
|
|
// remove black borders on left and right (or none at all if it already
|
|
|
|
// fits)
|
2020-04-25 06:19:44 +08:00
|
|
|
window_size.width = content_size.width * window_size.height
|
|
|
|
/ content_size.height;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 06:19:44 +08:00
|
|
|
return window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// initially, there is no current size, so use the frame size as current size
|
2019-11-04 01:00:11 +08:00
|
|
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
2021-10-30 21:20:39 +08:00
|
|
|
static inline struct sc_size
|
|
|
|
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
2019-11-04 01:00:11 +08:00
|
|
|
uint16_t req_height) {
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size window_size;
|
2019-11-04 01:00:11 +08:00
|
|
|
if (!req_width && !req_height) {
|
2021-12-21 02:17:15 +08:00
|
|
|
window_size = get_optimal_size(content_size, content_size, true);
|
2019-11-04 01:00:11 +08:00
|
|
|
} else {
|
|
|
|
if (req_width) {
|
|
|
|
window_size.width = req_width;
|
|
|
|
} else {
|
|
|
|
// compute from the requested height
|
2020-04-08 05:03:23 +08:00
|
|
|
window_size.width = (uint32_t) req_height * content_size.width
|
|
|
|
/ content_size.height;
|
2019-11-04 01:00:11 +08:00
|
|
|
}
|
|
|
|
if (req_height) {
|
|
|
|
window_size.height = req_height;
|
|
|
|
} else {
|
|
|
|
// compute from the requested width
|
2020-04-08 05:03:23 +08:00
|
|
|
window_size.height = (uint32_t) req_width * content_size.height
|
|
|
|
/ content_size.width;
|
2019-11-04 01:00:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return window_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2022-01-01 01:52:34 +08:00
|
|
|
static inline void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
|
2022-01-01 01:52:34 +08:00
|
|
|
if (SDL_SetRelativeMouseMode(capture)) {
|
|
|
|
LOGE("Could not set relative mouse mode to %s: %s",
|
|
|
|
capture ? "true" : "false", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
screen->mouse_captured = capture;
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_update_content_rect(struct sc_screen *screen) {
|
2020-04-18 00:44:24 +08:00
|
|
|
int dw;
|
|
|
|
int dh;
|
|
|
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size content_size = screen->content_size;
|
2020-04-18 00:44:24 +08:00
|
|
|
// The drawable size is the window size * the HiDPI scale
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size drawable_size = {dw, dh};
|
2020-04-18 00:44:24 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
static inline SDL_Texture *
|
2022-01-15 05:17:30 +08:00
|
|
|
create_texture(struct sc_screen *screen) {
|
2020-04-11 22:08:23 +08:00
|
|
|
SDL_Renderer *renderer = screen->renderer;
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size size = screen->frame_size;
|
2020-04-11 22:08:23 +08:00
|
|
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
size.width, size.height);
|
|
|
|
if (!texture) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (screen->mipmaps) {
|
|
|
|
struct sc_opengl *gl = &screen->gl;
|
|
|
|
|
|
|
|
SDL_GL_BindTexture(texture, NULL, NULL);
|
|
|
|
|
|
|
|
// Enable trilinear filtering for downscaling
|
|
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
|
|
GL_LINEAR_MIPMAP_LINEAR);
|
2020-05-23 20:32:16 +08:00
|
|
|
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
2020-04-11 22:08:23 +08:00
|
|
|
|
|
|
|
SDL_GL_UnbindTexture(texture);
|
|
|
|
}
|
|
|
|
|
|
|
|
return texture;
|
2018-03-27 17:01:40 +08:00
|
|
|
}
|
|
|
|
|
2021-11-13 05:39:36 +08:00
|
|
|
// 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
|
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
2021-11-13 05:39:36 +08:00
|
|
|
if (update_content_rect) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_update_content_rect(screen);
|
2021-11-13 05:39:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RenderClear(screen->renderer);
|
|
|
|
if (screen->rotation == 0) {
|
|
|
|
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
|
|
|
} else {
|
|
|
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
|
|
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
|
|
|
int cw_rotation = (4 - screen->rotation) % 4;
|
|
|
|
double angle = 90 * cw_rotation;
|
|
|
|
|
|
|
|
SDL_Rect *dstrect = NULL;
|
|
|
|
SDL_Rect rect;
|
|
|
|
if (screen->rotation & 1) {
|
|
|
|
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
|
|
|
|
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
|
|
|
|
rect.w = screen->rect.h;
|
|
|
|
rect.h = screen->rect.w;
|
|
|
|
dstrect = ▭
|
|
|
|
} else {
|
|
|
|
assert(screen->rotation == 2);
|
|
|
|
dstrect = &screen->rect;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
|
|
|
angle, NULL, 0);
|
|
|
|
}
|
|
|
|
SDL_RenderPresent(screen->renderer);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-14 04:14:09 +08:00
|
|
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
|
|
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
|
|
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
|
|
|
|
// not triggered. As a workaround, handle them in an event handler.
|
|
|
|
//
|
|
|
|
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
|
|
|
|
// <https://stackoverflow.com/a/40693139/1987178>
|
|
|
|
static int
|
|
|
|
event_watcher(void *data, SDL_Event *event) {
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_screen *screen = data;
|
2021-04-14 04:14:09 +08:00
|
|
|
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.
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2021-04-14 04:14:09 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-04-11 21:01:05 +08:00
|
|
|
static bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
|
|
|
|
struct sc_screen *screen = DOWNCAST(sink);
|
2021-04-11 21:01:05 +08:00
|
|
|
(void) screen;
|
2021-04-11 21:01:05 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
screen->open = true;
|
|
|
|
#endif
|
2021-04-11 21:01:05 +08:00
|
|
|
|
|
|
|
// nothing to do, the screen is already open on the main thread
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
|
|
|
struct sc_screen *screen = DOWNCAST(sink);
|
2021-04-11 21:01:05 +08:00
|
|
|
(void) screen;
|
2021-04-11 21:01:05 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
screen->open = false;
|
|
|
|
#endif
|
2021-04-11 21:01:05 +08:00
|
|
|
|
|
|
|
// nothing to do, the screen lifecycle is not managed by the frame producer
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
|
|
|
struct sc_screen *screen = DOWNCAST(sink);
|
2021-07-04 18:42:22 +08:00
|
|
|
return sc_video_buffer_push(&screen->vb, frame);
|
|
|
|
}
|
2021-04-11 21:01:05 +08:00
|
|
|
|
2021-07-04 18:42:22 +08:00
|
|
|
static void
|
|
|
|
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|
|
|
void *userdata) {
|
|
|
|
(void) vb;
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_screen *screen = userdata;
|
2021-04-11 21:01:05 +08:00
|
|
|
|
2021-10-31 02:21:00 +08:00
|
|
|
// event_failed implies previous_skipped (the previous frame may not have
|
|
|
|
// been consumed if the event was not sent)
|
|
|
|
assert(!screen->event_failed || previous_skipped);
|
|
|
|
|
|
|
|
bool need_new_event;
|
2021-07-04 18:42:22 +08:00
|
|
|
if (previous_skipped) {
|
2021-05-17 00:26:20 +08:00
|
|
|
fps_counter_add_skipped_frame(&screen->fps_counter);
|
2021-04-11 21:01:05 +08:00
|
|
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
2021-10-31 02:21:00 +08:00
|
|
|
// this new frame instead, unless the previous event failed
|
|
|
|
need_new_event = screen->event_failed;
|
2021-04-11 21:01:05 +08:00
|
|
|
} else {
|
2021-10-31 02:21:00 +08:00
|
|
|
need_new_event = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_new_event) {
|
2021-04-11 21:01:05 +08:00
|
|
|
static SDL_Event new_frame_event = {
|
|
|
|
.type = EVENT_NEW_FRAME,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Post the event on the UI thread
|
2021-10-31 02:21:00 +08:00
|
|
|
int ret = SDL_PushEvent(&new_frame_event);
|
|
|
|
if (ret < 0) {
|
|
|
|
LOGW("Could not post new frame event: %s", SDL_GetError());
|
|
|
|
screen->event_failed = true;
|
|
|
|
} else {
|
|
|
|
screen->event_failed = false;
|
|
|
|
}
|
2021-04-11 21:01:05 +08:00
|
|
|
}
|
2021-04-11 21:01:05 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_init(struct sc_screen *screen,
|
|
|
|
const struct sc_screen_params *params) {
|
2021-02-26 05:06:33 +08:00
|
|
|
screen->resize_pending = false;
|
|
|
|
screen->has_frame = false;
|
|
|
|
screen->fullscreen = false;
|
|
|
|
screen->maximized = false;
|
2021-10-31 02:21:00 +08:00
|
|
|
screen->event_failed = false;
|
2022-01-01 01:52:34 +08:00
|
|
|
screen->mouse_captured = false;
|
|
|
|
screen->mouse_capture_key_pressed = 0;
|
2021-02-26 05:06:33 +08:00
|
|
|
|
2021-07-04 18:42:22 +08:00
|
|
|
static const struct sc_video_buffer_callbacks cbs = {
|
|
|
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
|
|
|
};
|
|
|
|
|
2021-07-07 01:04:05 +08:00
|
|
|
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
|
|
|
|
screen);
|
2021-04-11 21:01:05 +08:00
|
|
|
if (!ok) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-04 21:24:51 +08:00
|
|
|
ok = sc_video_buffer_start(&screen->vb);
|
|
|
|
if (!ok) {
|
|
|
|
goto error_destroy_video_buffer;
|
|
|
|
}
|
|
|
|
|
2021-05-17 00:26:20 +08:00
|
|
|
if (!fps_counter_init(&screen->fps_counter)) {
|
2021-07-04 21:24:51 +08:00
|
|
|
goto error_stop_and_join_video_buffer;
|
2021-05-17 00:26:20 +08:00
|
|
|
}
|
|
|
|
|
2021-02-26 05:00:34 +08:00
|
|
|
screen->frame_size = params->frame_size;
|
|
|
|
screen->rotation = params->rotation;
|
|
|
|
if (screen->rotation) {
|
|
|
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
2020-04-08 16:17:12 +08:00
|
|
|
}
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size content_size =
|
2021-02-26 05:00:34 +08:00
|
|
|
get_rotated_size(screen->frame_size, screen->rotation);
|
2020-04-08 20:11:23 +08:00
|
|
|
screen->content_size = content_size;
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size window_size =
|
|
|
|
get_initial_optimal_size(content_size,params->window_width,
|
|
|
|
params->window_height);
|
2021-04-04 21:00:13 +08:00
|
|
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
|
|
|
| SDL_WINDOW_RESIZABLE
|
|
|
|
| SDL_WINDOW_ALLOW_HIGHDPI;
|
2021-02-26 05:00:34 +08:00
|
|
|
if (params->always_on_top) {
|
2019-01-27 19:04:22 +08:00
|
|
|
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
|
|
|
}
|
2021-02-26 05:00:34 +08:00
|
|
|
if (params->window_borderless) {
|
2019-08-29 13:25:17 +08:00
|
|
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
|
|
|
}
|
2019-01-27 19:04:22 +08:00
|
|
|
|
2021-02-26 05:00:34 +08:00
|
|
|
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
|
|
|
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
|
|
|
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
|
|
|
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
|
|
|
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
2019-03-03 03:09:56 +08:00
|
|
|
window_size.width, window_size.height,
|
|
|
|
window_flags);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->window) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create window: %s", SDL_GetError());
|
2021-05-17 00:36:07 +08:00
|
|
|
goto error_destroy_fps_counter;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
|
|
|
SDL_RENDERER_ACCELERATED);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->renderer) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create renderer: %s", SDL_GetError());
|
2021-05-17 00:36:07 +08:00
|
|
|
goto error_destroy_window;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-04-11 20:34:41 +08:00
|
|
|
SDL_RendererInfo renderer_info;
|
|
|
|
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
|
|
|
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
|
|
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
|
|
|
|
2021-02-22 04:47:55 +08:00
|
|
|
screen->mipmaps = false;
|
|
|
|
|
2020-04-25 05:01:58 +08:00
|
|
|
// starts with "opengl"
|
2021-02-08 02:12:14 +08:00
|
|
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
|
|
|
if (use_opengl) {
|
2020-04-11 22:08:23 +08:00
|
|
|
struct sc_opengl *gl = &screen->gl;
|
|
|
|
sc_opengl_init(gl);
|
|
|
|
|
|
|
|
LOGI("OpenGL version: %s", gl->version);
|
|
|
|
|
2021-02-26 05:00:34 +08:00
|
|
|
if (params->mipmaps) {
|
2020-04-12 05:55:29 +08:00
|
|
|
bool supports_mipmaps =
|
|
|
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
|
|
|
2, 0 /* OpenGL ES 2.0+ */);
|
|
|
|
if (supports_mipmaps) {
|
|
|
|
LOGI("Trilinear filtering enabled");
|
|
|
|
screen->mipmaps = true;
|
|
|
|
} else {
|
|
|
|
LOGW("Trilinear filtering disabled "
|
|
|
|
"(OpenGL 3.0+ or ES 2.0+ required)");
|
|
|
|
}
|
2020-04-11 22:08:23 +08:00
|
|
|
} else {
|
2020-04-12 05:55:29 +08:00
|
|
|
LOGI("Trilinear filtering disabled");
|
2020-04-11 22:08:23 +08:00
|
|
|
}
|
2021-02-26 05:00:34 +08:00
|
|
|
} else if (params->mipmaps) {
|
2020-05-23 20:23:30 +08:00
|
|
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
2020-04-11 22:08:23 +08:00
|
|
|
}
|
|
|
|
|
2021-10-23 00:51:20 +08:00
|
|
|
SDL_Surface *icon = scrcpy_icon_load();
|
2019-05-24 02:58:08 +08:00
|
|
|
if (icon) {
|
|
|
|
SDL_SetWindowIcon(screen->window, icon);
|
2021-10-23 00:51:20 +08:00
|
|
|
scrcpy_icon_destroy(icon);
|
2019-05-24 02:58:08 +08:00
|
|
|
} else {
|
|
|
|
LOGW("Could not load icon");
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-02-26 05:00:34 +08:00
|
|
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
|
|
|
|
params->frame_size.height);
|
2020-04-11 22:08:23 +08:00
|
|
|
screen->texture = create_texture(screen);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->texture) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create texture: %s", SDL_GetError());
|
2021-05-17 00:36:07 +08:00
|
|
|
goto error_destroy_renderer;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-04-11 21:01:05 +08:00
|
|
|
screen->frame = av_frame_alloc();
|
|
|
|
if (!screen->frame) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2021-05-17 00:36:07 +08:00
|
|
|
goto error_destroy_texture;
|
2021-04-11 21:01:05 +08:00
|
|
|
}
|
|
|
|
|
2021-12-31 23:32:07 +08:00
|
|
|
struct input_manager_params im_params = {
|
|
|
|
.controller = params->controller,
|
|
|
|
.screen = screen,
|
|
|
|
.kp = params->kp,
|
|
|
|
.mp = params->mp,
|
|
|
|
.control = params->control,
|
|
|
|
.forward_all_clicks = params->forward_all_clicks,
|
|
|
|
.legacy_paste = params->legacy_paste,
|
|
|
|
.clipboard_autosync = params->clipboard_autosync,
|
|
|
|
.shortcut_mods = params->shortcut_mods,
|
|
|
|
};
|
|
|
|
|
|
|
|
input_manager_init(&screen->im, &im_params);
|
|
|
|
|
2020-05-18 23:56:22 +08:00
|
|
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
|
|
|
// HiDPI issues with some SDL renderers when several displays having
|
|
|
|
// different HiDPI scaling are connected
|
|
|
|
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_update_content_rect(screen);
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2021-04-14 04:10:45 +08:00
|
|
|
if (params->fullscreen) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_switch_fullscreen(screen);
|
2021-04-14 04:10:45 +08:00
|
|
|
}
|
|
|
|
|
2021-04-14 04:14:09 +08:00
|
|
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
|
|
SDL_AddEventWatch(event_watcher, screen);
|
|
|
|
#endif
|
|
|
|
|
2021-04-11 21:01:05 +08:00
|
|
|
static const struct sc_frame_sink_ops ops = {
|
2022-01-15 05:17:30 +08:00
|
|
|
.open = sc_screen_frame_sink_open,
|
|
|
|
.close = sc_screen_frame_sink_close,
|
|
|
|
.push = sc_screen_frame_sink_push,
|
2021-04-11 21:01:05 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
screen->frame_sink.ops = &ops;
|
|
|
|
|
2021-04-11 21:01:05 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
screen->open = false;
|
|
|
|
#endif
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2021-05-17 00:36:07 +08:00
|
|
|
|
|
|
|
error_destroy_texture:
|
|
|
|
SDL_DestroyTexture(screen->texture);
|
|
|
|
error_destroy_renderer:
|
|
|
|
SDL_DestroyRenderer(screen->renderer);
|
|
|
|
error_destroy_window:
|
|
|
|
SDL_DestroyWindow(screen->window);
|
|
|
|
error_destroy_fps_counter:
|
|
|
|
fps_counter_destroy(&screen->fps_counter);
|
2021-07-04 21:24:51 +08:00
|
|
|
error_stop_and_join_video_buffer:
|
|
|
|
sc_video_buffer_stop(&screen->vb);
|
|
|
|
sc_video_buffer_join(&screen->vb);
|
2021-05-17 00:36:07 +08:00
|
|
|
error_destroy_video_buffer:
|
2021-06-26 21:02:18 +08:00
|
|
|
sc_video_buffer_destroy(&screen->vb);
|
2021-05-17 00:36:07 +08:00
|
|
|
|
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-04-14 04:04:38 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_show_window(struct sc_screen *screen) {
|
Improve startup time
On startup, the client has to:
1. listen on a port
2. push and start the server to the device
3. wait for the server to connect (accept)
4. read device name and size
5. initialize SDL
6. initialize the window and renderer
7. show the window
From the execution of the app_process command to start the server on the
device, to the execution of the java main method, it takes ~800ms. As a
consequence, step 3 also takes ~800ms on the client.
Once complete, the client initializes SDL, which takes ~500ms.
These two expensive actions are executed sequentially:
HOST DEVICE
listen on port | |
push/start the server |----------------->|| app_process loads the jar
accept the connection . ^ ||
. | ||
. | WASTE ||
. | OF ||
. | TIME ||
. | ||
. | ||
. v X execution of our java main
connection accepted |<-----------------| connect to the host
init SDL || |
|| ,----------------| send frames
|| |,---------------|
|| ||,--------------|
|| |||,-------------|
|| ||||,------------|
init window/renderer | |||||,-----------|
display frames |<++++++-----------|
(many frames skipped)
The rationale for step 3 occuring before step 5 is that initializing
SDL replaces the SIGTERM handler to receive the event in the event loop,
so pressing Ctrl+C during step 5 would not work (since it blocks the
event loop).
But this is not so important; let's parallelize the SDL initialization
with the app_process execution (we'll just add a timeout to the
connection):
HOST DEVICE
listen on port | |
push/start the server |----------------->||app_process loads the jar
init SDL || ||
|| ||
|| ||
|| ||
|| ||
|| ||
accept the connection . ||
. X execution of our java main
connection accepted |<-----------------| connect to the host
init window/renderer | |
display frames |<-----------------| send frames
|<-----------------|
In addition, show the window only once the first frame is available to
avoid flickering (opening a black window for 100~200ms).
Note: the window and renderer are initialized after the connection is
accepted because they use the device information received from the
device.
2018-02-09 20:50:54 +08:00
|
|
|
SDL_ShowWindow(screen->window);
|
|
|
|
}
|
|
|
|
|
2021-04-14 04:22:54 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_hide_window(struct sc_screen *screen) {
|
2021-04-14 04:22:54 +08:00
|
|
|
SDL_HideWindow(screen->window);
|
|
|
|
}
|
|
|
|
|
2021-05-17 00:26:20 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_interrupt(struct sc_screen *screen) {
|
2021-07-04 21:24:51 +08:00
|
|
|
sc_video_buffer_stop(&screen->vb);
|
2021-05-17 00:26:20 +08:00
|
|
|
fps_counter_interrupt(&screen->fps_counter);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_join(struct sc_screen *screen) {
|
2021-07-04 21:24:51 +08:00
|
|
|
sc_video_buffer_join(&screen->vb);
|
2021-05-17 00:26:20 +08:00
|
|
|
fps_counter_join(&screen->fps_counter);
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_destroy(struct sc_screen *screen) {
|
2021-04-11 21:01:05 +08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
assert(!screen->open);
|
|
|
|
#endif
|
2021-04-11 21:01:05 +08:00
|
|
|
av_frame_free(&screen->frame);
|
2021-02-13 21:40:00 +08:00
|
|
|
SDL_DestroyTexture(screen->texture);
|
|
|
|
SDL_DestroyRenderer(screen->renderer);
|
|
|
|
SDL_DestroyWindow(screen->window);
|
2021-05-17 00:26:20 +08:00
|
|
|
fps_counter_destroy(&screen->fps_counter);
|
2021-06-26 21:02:18 +08:00
|
|
|
sc_video_buffer_destroy(&screen->vb);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size new_content_size) {
|
|
|
|
struct sc_size window_size = get_window_size(screen);
|
|
|
|
struct sc_size target_size = {
|
2020-05-11 09:11:13 +08:00
|
|
|
.width = (uint32_t) window_size.width * new_content_size.width
|
2020-05-11 08:00:46 +08:00
|
|
|
/ old_content_size.width,
|
2020-05-11 09:11:13 +08:00
|
|
|
.height = (uint32_t) window_size.height * new_content_size.height
|
2020-05-11 08:00:46 +08:00
|
|
|
/ old_content_size.height,
|
|
|
|
};
|
2021-12-21 02:17:15 +08:00
|
|
|
target_size = get_optimal_size(target_size, new_content_size, true);
|
2020-05-11 08:00:46 +08:00
|
|
|
set_window_size(screen, target_size);
|
2020-05-11 09:11:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
2020-05-11 09:11:13 +08:00
|
|
|
if (!screen->fullscreen && !screen->maximized) {
|
|
|
|
resize_for_content(screen, screen->content_size, new_content_size);
|
|
|
|
} else if (!screen->resize_pending) {
|
|
|
|
// Store the windowed size to be able to compute the optimal size once
|
|
|
|
// fullscreen and maximized are disabled
|
|
|
|
screen->windowed_content_size = screen->content_size;
|
|
|
|
screen->resize_pending = true;
|
|
|
|
}
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
screen->content_size = new_content_size;
|
|
|
|
}
|
|
|
|
|
2020-05-11 09:11:13 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
apply_pending_resize(struct sc_screen *screen) {
|
2020-05-11 09:11:13 +08:00
|
|
|
assert(!screen->fullscreen);
|
|
|
|
assert(!screen->maximized);
|
|
|
|
if (screen->resize_pending) {
|
|
|
|
resize_for_content(screen, screen->windowed_content_size,
|
|
|
|
screen->content_size);
|
|
|
|
screen->resize_pending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 05:03:23 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
2020-04-08 05:03:23 +08:00
|
|
|
assert(rotation < 4);
|
|
|
|
if (rotation == screen->rotation) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size new_content_size =
|
2020-04-08 05:03:23 +08:00
|
|
|
get_rotated_size(screen->frame_size, rotation);
|
|
|
|
|
2020-05-11 08:00:46 +08:00
|
|
|
set_content_size(screen, new_content_size);
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2020-04-08 05:03:23 +08:00
|
|
|
screen->rotation = rotation;
|
|
|
|
LOGI("Display rotation set to %u", rotation);
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2020-04-08 05:03:23 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 18:14:13 +08:00
|
|
|
// recreate the texture and resize the window if the frame size has changed
|
2019-03-03 06:52:22 +08:00
|
|
|
static bool
|
2022-01-15 05:17:30 +08:00
|
|
|
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
2019-03-03 03:09:56 +08:00
|
|
|
if (screen->frame_size.width != new_frame_size.width
|
|
|
|
|| screen->frame_size.height != new_frame_size.height) {
|
2018-02-08 18:14:13 +08:00
|
|
|
// frame dimension changed, destroy texture
|
|
|
|
SDL_DestroyTexture(screen->texture);
|
|
|
|
|
|
|
|
screen->frame_size = new_frame_size;
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size new_content_size =
|
2020-04-18 00:44:24 +08:00
|
|
|
get_rotated_size(new_frame_size, screen->rotation);
|
|
|
|
set_content_size(screen, new_content_size);
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_update_content_rect(screen);
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2019-03-08 03:17:09 +08:00
|
|
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
2018-02-08 18:14:13 +08:00
|
|
|
screen->frame_size.width, screen->frame_size.height);
|
2020-04-11 22:08:23 +08:00
|
|
|
screen->texture = create_texture(screen);
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!screen->texture) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGC("Could not create texture: %s", SDL_GetError());
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// write the frame into the texture
|
2019-03-03 03:09:56 +08:00
|
|
|
static void
|
2022-01-15 05:17:30 +08:00
|
|
|
update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
2018-02-08 18:14:13 +08:00
|
|
|
SDL_UpdateYUVTexture(screen->texture, NULL,
|
|
|
|
frame->data[0], frame->linesize[0],
|
|
|
|
frame->data[1], frame->linesize[1],
|
|
|
|
frame->data[2], frame->linesize[2]);
|
2020-04-11 22:08:23 +08:00
|
|
|
|
|
|
|
if (screen->mipmaps) {
|
|
|
|
SDL_GL_BindTexture(screen->texture, NULL, NULL);
|
|
|
|
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
SDL_GL_UnbindTexture(screen->texture);
|
|
|
|
}
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
static bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_update_frame(struct sc_screen *screen) {
|
2021-04-11 21:01:05 +08:00
|
|
|
av_frame_unref(screen->frame);
|
2021-06-26 21:02:18 +08:00
|
|
|
sc_video_buffer_consume(&screen->vb, screen->frame);
|
2021-04-11 21:01:05 +08:00
|
|
|
AVFrame *frame = screen->frame;
|
2021-02-20 03:56:09 +08:00
|
|
|
|
2021-05-17 00:26:20 +08:00
|
|
|
fps_counter_add_rendered_frame(&screen->fps_counter);
|
2021-02-20 03:56:09 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size new_frame_size = {frame->width, frame->height};
|
2018-02-08 18:14:13 +08:00
|
|
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
update_texture(screen, frame);
|
2018-02-09 18:14:47 +08:00
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, false);
|
2019-03-03 06:52:22 +08:00
|
|
|
return true;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
2019-03-03 06:52:22 +08:00
|
|
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
2018-02-08 18:14:13 +08:00
|
|
|
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
2018-02-08 18:14:13 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
screen->fullscreen = !screen->fullscreen;
|
2020-05-11 09:11:13 +08:00
|
|
|
if (!screen->fullscreen && !screen->maximized) {
|
|
|
|
apply_pending_resize(screen);
|
|
|
|
}
|
2018-02-08 18:14:13 +08:00
|
|
|
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
2020-05-16 00:53:43 +08:00
|
|
|
if (screen->fullscreen || screen->maximized) {
|
2019-11-12 04:44:27 +08:00
|
|
|
return;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-11-12 04:44:27 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_point point = get_window_position(screen);
|
|
|
|
struct sc_size window_size = get_window_size(screen);
|
2021-06-15 03:24:51 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size optimal_size =
|
2021-12-21 02:17:15 +08:00
|
|
|
get_optimal_size(window_size, screen->content_size, false);
|
2021-06-15 03:24:51 +08:00
|
|
|
|
|
|
|
// Center the window related to the device screen
|
|
|
|
assert(optimal_size.width <= window_size.width);
|
|
|
|
assert(optimal_size.height <= window_size.height);
|
|
|
|
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
|
|
|
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
|
|
|
|
2019-11-12 04:44:27 +08:00
|
|
|
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
2021-06-15 03:24:51 +08:00
|
|
|
SDL_SetWindowPosition(screen->window, new_x, new_y);
|
2020-04-18 06:31:31 +08:00
|
|
|
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
|
|
|
optimal_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
2019-11-12 04:44:27 +08:00
|
|
|
if (screen->fullscreen) {
|
|
|
|
return;
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-11-12 04:44:27 +08:00
|
|
|
|
|
|
|
if (screen->maximized) {
|
|
|
|
SDL_RestoreWindow(screen->window);
|
|
|
|
screen->maximized = false;
|
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_size content_size = screen->content_size;
|
2020-04-08 05:03:23 +08:00
|
|
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
2020-04-18 06:31:31 +08:00
|
|
|
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
|
|
|
content_size.height);
|
2018-02-08 18:14:13 +08:00
|
|
|
}
|
2019-10-20 21:32:33 +08:00
|
|
|
|
2022-01-01 01:52:34 +08:00
|
|
|
static inline bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
|
2022-01-01 01:52:34 +08:00
|
|
|
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
|
|
|
|
}
|
|
|
|
|
2021-02-16 01:44:53 +08:00
|
|
|
bool
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
2021-02-16 01:44:53 +08:00
|
|
|
switch (event->type) {
|
|
|
|
case EVENT_NEW_FRAME:
|
|
|
|
if (!screen->has_frame) {
|
|
|
|
screen->has_frame = true;
|
|
|
|
// this is the very first frame, show the window
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_show_window(screen);
|
2021-02-16 01:44:53 +08:00
|
|
|
}
|
2022-01-15 05:17:30 +08:00
|
|
|
bool ok = sc_screen_update_frame(screen);
|
2021-02-16 01:44:53 +08:00
|
|
|
if (!ok) {
|
|
|
|
LOGW("Frame update failed\n");
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
if (!screen->has_frame) {
|
|
|
|
// Do nothing
|
|
|
|
return true;
|
|
|
|
}
|
2021-02-16 01:47:17 +08:00
|
|
|
switch (event->window.event) {
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED:
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2021-02-16 01:47:17 +08:00
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2021-02-16 01:47:17 +08:00
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
|
|
|
screen->maximized = true;
|
|
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_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;
|
|
|
|
apply_pending_resize(screen);
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_render(screen, true);
|
2021-02-16 01:47:17 +08:00
|
|
|
break;
|
2022-01-01 01:52:34 +08:00
|
|
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
|
|
if (screen->im.mp->relative_mode) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_capture_mouse(screen, false);
|
2022-01-01 01:52:34 +08:00
|
|
|
}
|
|
|
|
break;
|
2021-02-16 01:47:17 +08:00
|
|
|
}
|
2021-02-16 01:44:53 +08:00
|
|
|
return true;
|
2022-01-01 01:52:34 +08:00
|
|
|
case SDL_KEYDOWN:
|
|
|
|
if (screen->im.mp->relative_mode) {
|
|
|
|
SDL_Keycode key = event->key.keysym.sym;
|
2022-01-15 05:17:30 +08:00
|
|
|
if (sc_screen_is_mouse_capture_key(key)) {
|
2022-01-01 01:52:34 +08:00
|
|
|
if (!screen->mouse_capture_key_pressed) {
|
|
|
|
screen->mouse_capture_key_pressed = key;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// Another mouse capture key has been pressed, cancel
|
|
|
|
// mouse (un)capture
|
|
|
|
screen->mouse_capture_key_pressed = 0;
|
|
|
|
// Do not return, the event must be forwarded to the
|
|
|
|
// input manager
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_KEYUP:
|
|
|
|
if (screen->im.mp->relative_mode) {
|
|
|
|
SDL_Keycode key = event->key.keysym.sym;
|
|
|
|
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
|
|
|
screen->mouse_capture_key_pressed = 0;
|
|
|
|
if (key == cap) {
|
|
|
|
// A mouse capture key has been pressed then released:
|
|
|
|
// toggle the capture mouse mode
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_capture_mouse(screen, !screen->mouse_captured);
|
2022-01-01 01:52:34 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Do not return, the event must be forwarded to the input
|
|
|
|
// manager
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_MOUSEWHEEL:
|
|
|
|
case SDL_MOUSEMOTION:
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
|
|
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
|
|
|
// Do not forward to input manager, the mouse will be captured
|
|
|
|
// on SDL_MOUSEBUTTONUP
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_FINGERMOTION:
|
|
|
|
case SDL_FINGERDOWN:
|
|
|
|
case SDL_FINGERUP:
|
|
|
|
if (screen->im.mp->relative_mode) {
|
|
|
|
// Touch events are not compatible with relative mode
|
|
|
|
// (coordinates are not relative)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
|
|
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_capture_mouse(screen, true);
|
2022-01-01 01:52:34 +08:00
|
|
|
return true;
|
|
|
|
}
|
2021-02-16 01:44:53 +08:00
|
|
|
}
|
|
|
|
|
2021-12-31 23:32:07 +08:00
|
|
|
return input_manager_handle_event(&screen->im, event);
|
2021-02-16 01:44:53 +08:00
|
|
|
}
|
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_point
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
|
|
|
int32_t x, int32_t y) {
|
2020-04-18 00:43:29 +08:00
|
|
|
unsigned rotation = screen->rotation;
|
|
|
|
assert(rotation < 4);
|
|
|
|
|
|
|
|
int32_t w = screen->content_size.width;
|
|
|
|
int32_t h = screen->content_size.height;
|
2020-04-18 00:44:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
|
|
|
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
|
|
|
|
|
|
|
// rotate
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_point result;
|
2020-04-18 00:43:29 +08:00
|
|
|
switch (rotation) {
|
|
|
|
case 0:
|
|
|
|
result.x = x;
|
|
|
|
result.y = y;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
result.x = h - y;
|
|
|
|
result.y = x;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
result.x = w - x;
|
|
|
|
result.y = h - y;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(rotation == 3);
|
|
|
|
result.x = y;
|
|
|
|
result.y = w - x;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-04-18 00:44:24 +08:00
|
|
|
|
2021-10-30 21:20:39 +08:00
|
|
|
struct sc_point
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
|
|
|
int32_t x, int32_t y) {
|
|
|
|
sc_screen_hidpi_scale_coords(screen, &x, &y);
|
|
|
|
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
|
2020-06-25 14:54:40 +08:00
|
|
|
}
|
|
|
|
|
2020-04-18 00:44:24 +08:00
|
|
|
void
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
|
2020-04-18 00:44:24 +08:00
|
|
|
// 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;
|
|
|
|
}
|