diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index e2a65045b..2384a64ac 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -345,6 +345,7 @@ struct SDL_VideoDevice Uint32 next_object_id; char *clipboard_text; SDL_bool setting_display_mode; + SDL_bool disable_display_mode_switching; /* * * */ /* Data used by the GL drivers */ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index f2c7b8b23..6537b4535 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1339,14 +1339,17 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen) resized = SDL_FALSE; } - /* only do the mode change if we want exclusive fullscreen */ - if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) { - if (SDL_SetDisplayModeForDisplay(display, &fullscreen_mode) < 0) { - return -1; - } - } else { - if (SDL_SetDisplayModeForDisplay(display, NULL) < 0) { - return -1; + /* Don't try to change the display mode if the driver doesn't want it. */ + if (_this->disable_display_mode_switching == SDL_FALSE) { + /* only do the mode change if we want exclusive fullscreen */ + if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) { + if (SDL_SetDisplayModeForDisplay(display, &fullscreen_mode) < 0) { + return -1; + } + } else { + if (SDL_SetDisplayModeForDisplay(display, NULL) < 0) { + return -1; + } } } diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 2f0ae27f2..5b26152d8 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -389,8 +389,10 @@ pointer_handle_motion(void *data, struct wl_pointer *pointer, input->sx_w = sx_w; input->sy_w = sy_w; if (input->pointer_focus) { - const int sx = wl_fixed_to_int(sx_w); - const int sy = wl_fixed_to_int(sy_w); + const float sx_f = (float)wl_fixed_to_double(sx_w); + const float sy_f = (float)wl_fixed_to_double(sy_w); + const int sx = (int)SDL_lroundf(sx_f * window->pointer_scale); + const int sy = (int)SDL_lroundf(sy_f * window->pointer_scale); SDL_SendMouseMotion(window->sdlwindow, 0, 0, sx, sy); } } @@ -717,8 +719,8 @@ touch_handler_down(void *data, struct wl_touch *touch, unsigned int serial, int id, wl_fixed_t fx, wl_fixed_t fy) { SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(surface); - const double dblx = wl_fixed_to_double(fx); - const double dbly = wl_fixed_to_double(fy); + const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale; + const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale; const float x = dblx / window_data->sdlwindow->w; const float y = dbly / window_data->sdlwindow->h; @@ -750,8 +752,8 @@ touch_handler_motion(void *data, struct wl_touch *touch, unsigned int timestamp, int id, wl_fixed_t fx, wl_fixed_t fy) { SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(touch_surface(id)); - const double dblx = wl_fixed_to_double(fx); - const double dbly = wl_fixed_to_double(fy); + const double dblx = wl_fixed_to_double(fx) * window_data->pointer_scale; + const double dbly = wl_fixed_to_double(fy) * window_data->pointer_scale; const float x = dblx / window_data->sdlwindow->w; const float y = dbly / window_data->sdlwindow->h; @@ -1817,8 +1819,10 @@ tablet_tool_handle_motion(void* data, struct zwp_tablet_tool_v2* tool, wl_fixed_ input->sx_w = sx_w; input->sy_w = sy_w; if (input->tool_focus) { - const int sx = wl_fixed_to_int(sx_w); - const int sy = wl_fixed_to_int(sy_w); + const float sx_f = (float)wl_fixed_to_double(sx_w); + const float sy_f = (float)wl_fixed_to_double(sy_w); + const int sx = (int)SDL_lroundf(sx_f * window->pointer_scale); + const int sy = (int)SDL_lroundf(sy_f * window->pointer_scale); SDL_SendMouseMotion(window->sdlwindow, 0, 0, sx, sy); } } @@ -2345,12 +2349,19 @@ int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *wi if (SDL_RectEmpty(&window->mouse_rect)) { confine_rect = NULL; } else { + SDL_Rect scaled_mouse_rect; + + scaled_mouse_rect.x = (int)SDL_floorf((float)window->mouse_rect.x / w->pointer_scale); + scaled_mouse_rect.y = (int)SDL_floorf((float)window->mouse_rect.y / w->pointer_scale); + scaled_mouse_rect.w = (int)SDL_ceilf((float)window->mouse_rect.w / w->pointer_scale); + scaled_mouse_rect.h = (int)SDL_ceilf((float)window->mouse_rect.h / w->pointer_scale); + confine_rect = wl_compositor_create_region(d->compositor); wl_region_add(confine_rect, - window->mouse_rect.x, - window->mouse_rect.y, - window->mouse_rect.w, - window->mouse_rect.h); + scaled_mouse_rect.x, + scaled_mouse_rect.y, + scaled_mouse_rect.w, + scaled_mouse_rect.h); } confined_pointer = diff --git a/src/video/wayland/SDL_waylandopengles.c b/src/video/wayland/SDL_waylandopengles.c index 6b10cbee7..4c834fd86 100644 --- a/src/video/wayland/SDL_waylandopengles.c +++ b/src/video/wayland/SDL_waylandopengles.c @@ -205,11 +205,11 @@ Wayland_GLES_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h) data = (SDL_WindowData *) window->driverdata; if (w) { - *w = window->w * data->scale_factor; + *w = data->drawable_width; } if (h) { - *h = window->h * data->scale_factor; + *h = data->drawable_height; } } } diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 77925ad74..a0f27fc8a 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -54,6 +54,7 @@ #include "text-input-unstable-v3-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#include "viewporter-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -278,6 +279,8 @@ Wayland_CreateDevice(int devindex) device->free = Wayland_DeleteDevice; + device->disable_display_mode_switching = SDL_TRUE; + return device; } @@ -321,6 +324,7 @@ xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, return; } } + driverdata->width = width; driverdata->height = height; driverdata->has_logical_size = SDL_TRUE; @@ -360,6 +364,103 @@ static const struct zxdg_output_v1_listener xdg_output_listener = { xdg_output_handle_description, }; +static void +AddEmulatedModes(SDL_VideoDisplay *dpy, SDL_bool rot_90) +{ + struct EmulatedMode + { + int w; + int h; + }; + + /* Resolution lists courtesy of XWayland */ + const struct EmulatedMode modes_4x3[] = { + /* 4:3 (1.33) */ + { 2048, 1536 }, + { 1920, 1440 }, + { 1600, 1200 }, + { 1440, 1080 }, + { 1400, 1050 }, + { 1280, 1024 }, + { 1280, 960 }, + { 1152, 864 }, + { 1024, 768 }, + { 800, 600 }, + { 640, 480 }, + { 320, 240 } + }; + + const struct EmulatedMode modes_16x10[] = { + /* 16:10 (1.6) */ + { 2560, 1600 }, + { 1920, 1200 }, + { 1680, 1050 }, + { 1440, 900 }, + { 1280, 800 }, + { 720, 480 }, + { 640, 400 }, + { 320, 200 } + }; + + const struct EmulatedMode modes_16x9[] = { + /* 16:9 (1.77) */ + { 7680, 4320 }, + { 6144, 3160 }, + { 5120, 2880 }, + { 4096, 2304 }, + { 3840, 2160 }, + { 3200, 1800 }, + { 2880, 1620 }, + { 2560, 1440 }, + { 2048, 1152 }, + { 1920, 1080 }, + { 1600, 900 }, + { 1368, 768 }, + { 1280, 720 }, + { 1024, 768 }, + { 864, 486 }, + { 720, 400 }, + { 640, 350 } + }; + + const struct EmulatedMode *mode_list = NULL; + int mode_list_size; + const int native_width = (float)dpy->display_modes->w; + const int native_height = (float)dpy->display_modes->h; + const float aspect = (float)native_width / (float)native_height; + + if (aspect >= 1.7f) { /* 16x9 (1.77) */ + mode_list = modes_16x9; + mode_list_size = SDL_arraysize(modes_16x9); + } else if (aspect >= 1.5f) { /* 16x10 (1.6) */ + mode_list = modes_16x10; + mode_list_size = SDL_arraysize(modes_16x10); + } else if (aspect >= 1.3f) { /* 4x3 (1.33) */ + mode_list = modes_4x3; + mode_list_size = SDL_arraysize(modes_4x3); + } else { + return; /* Some weird aspect we don't support */ + } + + for (int i = 0; i < mode_list_size; ++i) { + /* Only add modes that are smaller than the native mode */ + if ((mode_list[i].w < native_width && mode_list[i].h < native_height) || + (mode_list[i].w < native_width && mode_list[i].h == native_height)) { + SDL_DisplayMode mode = *dpy->display_modes; + + if (rot_90) { + mode.w = mode_list[i].h; + mode.h = mode_list[i].w; + } else { + mode.w = mode_list[i].w; + mode.h = mode_list[i].h; + } + + SDL_AddDisplayMode(dpy, &mode); + } + } +} + static void display_handle_geometry(void *data, struct wl_output *output, @@ -441,9 +542,11 @@ display_handle_mode(void *data, int refresh) { SDL_WaylandOutputData* driverdata = data; - SDL_DisplayMode mode; if (flags & WL_OUTPUT_MODE_CURRENT) { + driverdata->native_width = width; + driverdata->native_height = height; + /* * Don't rotate this yet, wl-output coordinates are transformed in * handle_done and xdg-output coordinates are pre-transformed. @@ -455,29 +558,6 @@ display_handle_mode(void *data, driverdata->refresh = refresh; } - - /* Note that the width/height are NOT multiplied by scale_factor! - * This is intentional and is designed to get the unscaled modes, which is - * important for high-DPI games intending to use the display mode as the - * target drawable size. The scaled desktop mode will be added at the end - * when display_handle_done is called (see below). - */ - SDL_zero(mode); - mode.format = SDL_PIXELFORMAT_RGB888; - if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { - mode.w = height; - mode.h = width; - } else { - mode.w = width; - mode.h = height; - } - mode.refresh_rate = (int)SDL_round(refresh / 1000.0); /* mHz to Hz */ - mode.driverdata = driverdata->output; - if (driverdata->index > -1) { - SDL_AddDisplayMode(SDL_GetDisplay(driverdata->index), &mode); - } else { - SDL_AddDisplayMode(&driverdata->placeholder, &mode); - } } static void @@ -485,7 +565,8 @@ display_handle_done(void *data, struct wl_output *output) { SDL_WaylandOutputData* driverdata = data; - SDL_DisplayMode mode; + SDL_VideoData* video = driverdata->videodata; + SDL_DisplayMode native_mode, desktop_mode; SDL_VideoDisplay *dpy; /* @@ -502,19 +583,51 @@ display_handle_done(void *data, return; } - SDL_zero(mode); - mode.format = SDL_PIXELFORMAT_RGB888; + /* The native display resolution */ + SDL_zero(native_mode); + native_mode.format = SDL_PIXELFORMAT_RGB888; - if (driverdata->has_logical_size) { - /* xdg-output dimensions are already transformed, so no need to rotate. */ - mode.w = driverdata->width; - mode.h = driverdata->height; - } else if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { - mode.w = driverdata->height / driverdata->scale_factor; - mode.h = driverdata->width / driverdata->scale_factor; + if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { + native_mode.w = driverdata->native_height; + native_mode.h = driverdata->native_width; } else { - mode.w = driverdata->width / driverdata->scale_factor; - mode.h = driverdata->height / driverdata->scale_factor; + native_mode.w = driverdata->native_width; + native_mode.h = driverdata->native_height; + } + native_mode.refresh_rate = (int)SDL_round(driverdata->refresh / 1000.0); /* mHz to Hz */ + native_mode.driverdata = driverdata->output; + + /* The scaled desktop mode */ + SDL_zero(desktop_mode); + desktop_mode.format = SDL_PIXELFORMAT_RGB888; + + /* Scale the desktop coordinates, if xdg-output isn't present */ + if (!driverdata->has_logical_size) { + driverdata->width /= driverdata->scale_factor; + driverdata->height /= driverdata->scale_factor; + } + + /* xdg-output dimensions are already transformed, so no need to rotate. */ + if (driverdata->has_logical_size || !(driverdata->transform & WL_OUTPUT_TRANSFORM_90)) { + desktop_mode.w = driverdata->width; + desktop_mode.h = driverdata->height; + } else { + desktop_mode.w = driverdata->height; + desktop_mode.h = driverdata->width; + } + desktop_mode.refresh_rate = (int)SDL_round(driverdata->refresh / 1000.0); /* mHz to Hz */ + desktop_mode.driverdata = driverdata->output; + + /* + * The native display mode is only exposed separately from the desktop size if: + * the desktop is scaled and the wp_viewporter protocol is supported. + */ + if (driverdata->scale_factor > 1.0f && video->viewporter != NULL) { + if (driverdata->index > -1) { + SDL_AddDisplayMode(SDL_GetDisplay(driverdata->index), &native_mode); + } else { + SDL_AddDisplayMode(&driverdata->placeholder, &native_mode); + } } /* Calculate the display DPI */ @@ -542,18 +655,20 @@ display_handle_done(void *data, ((float) driverdata->physical_height) / 25.4f); } - mode.refresh_rate = (int)SDL_round(driverdata->refresh / 1000.0); /* mHz to Hz */ - mode.driverdata = driverdata->output; - if (driverdata->index > -1) { dpy = SDL_GetDisplay(driverdata->index); } else { dpy = &driverdata->placeholder; } - SDL_AddDisplayMode(dpy, &mode); - SDL_SetCurrentDisplayMode(dpy, &mode); - SDL_SetDesktopDisplayMode(dpy, &mode); + SDL_AddDisplayMode(dpy, &desktop_mode); + SDL_SetCurrentDisplayMode(dpy, &desktop_mode); + SDL_SetDesktopDisplayMode(dpy, &desktop_mode); + + /* Add emulated modes if wp_viewporter is supported. */ + if (video->viewporter) { + AddEmulatedModes(dpy, (driverdata->transform & WL_OUTPUT_TRANSFORM_90) != 0); + } if (driverdata->index == -1) { /* First time getting display info, create the VideoDisplay */ @@ -600,7 +715,7 @@ Wayland_add_display(SDL_VideoData *d, uint32_t id) data->videodata = d; data->output = output; data->registry_id = id; - data->scale_factor = 1.0; + data->scale_factor = 1.0f; data->index = -1; wl_output_add_listener(output, &output_listener, data); @@ -756,6 +871,8 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id, version = SDL_min(version, 3); /* Versions 1 through 3 are supported. */ d->xdg_output_manager = wl_registry_bind(d->registry, id, &zxdg_output_manager_v1_interface, version); Wayland_init_xdg_output(d); + } else if (SDL_strcmp(interface, "wp_viewporter") == 0) { + d->viewporter = wl_registry_bind(d->registry, id, &wp_viewporter_interface, 1); #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH } else if (SDL_strcmp(interface, "qt_touch_extension") == 0) { @@ -945,6 +1062,10 @@ Wayland_VideoQuit(_THIS) zxdg_output_manager_v1_destroy(data->xdg_output_manager); } + if (data->viewporter) { + wp_viewporter_destroy(data->viewporter); + } + if (data->compositor) wl_compositor_destroy(data->compositor); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index dff2fe394..6f941db80 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -74,6 +74,7 @@ typedef struct { struct xdg_activation_v1 *activation_manager; struct zwp_text_input_manager_v3 *text_input_manager; struct zxdg_output_manager_v1 *xdg_output_manager; + struct wp_viewporter *viewporter; EGLDisplay edpy; EGLContext context; @@ -101,6 +102,7 @@ struct SDL_WaylandOutputData { struct zxdg_output_v1 *xdg_output; uint32_t registry_id; float scale_factor; + int native_width, native_height; int x, y, width, height, refresh, transform; SDL_DisplayOrientation orientation; int physical_width, physical_height; diff --git a/src/video/wayland/SDL_waylandvulkan.c b/src/video/wayland/SDL_waylandvulkan.c index eb4435131..90b318fc8 100644 --- a/src/video/wayland/SDL_waylandvulkan.c +++ b/src/video/wayland/SDL_waylandvulkan.c @@ -139,11 +139,11 @@ void Wayland_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h) data = (SDL_WindowData *) window->driverdata; if (w) { - *w = window->w * data->scale_factor; + *w = data->drawable_width; } if (h) { - *h = window->h * data->scale_factor; + *h = data->drawable_height; } } } diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 1bb5159eb..3056355a3 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -36,11 +36,215 @@ #include "xdg-decoration-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" +#include "viewporter-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include #endif +static void +GetFullScreenDimensions(SDL_Window *window, int *width, int *height, int *drawable_width, int *drawable_height) +{ + SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata; + + int fs_width, fs_height; + int buf_width, buf_height; + + /* + * Fullscreen desktop mandates a desktop sized window, so that's what applications will get. + * If the application is DPI aware, it will need to handle the transformations between the + * differently sized window and backbuffer spaces on its own. + */ + if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) { + fs_width = output->width; + fs_height = output->height; + + /* If the application is DPI aware, we can expose the true backbuffer size */ + if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { + buf_width = output->native_width; + buf_height = output->native_height; + } else { + buf_width = fs_width; + buf_height = fs_height; + } + } else { + /* + * If a mode was set, use it, otherwise use the native resolution + * for DPI aware apps and the desktop size for legacy apps. + */ + if (window->fullscreen_mode.w != 0 && window->fullscreen_mode.h != 0) { + fs_width = window->fullscreen_mode.w; + fs_height = window->fullscreen_mode.h; + } else if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { + fs_width = output->native_width; + fs_height = output->native_height; + } else { + fs_width = output->width; + fs_height = output->height; + } + + buf_width = fs_width; + buf_height = fs_height; + } + + if (width) { + *width = fs_width; + } + if (height) { + *height = fs_height; + } + if (drawable_width) { + *drawable_width = buf_width; + } + if (drawable_height) { + *drawable_height = buf_height; + } +} + +static inline SDL_bool +DesktopIsScaled(SDL_Window *window) +{ + SDL_WindowData *data = window->driverdata; + + return data->scale_factor != 1.0f; +} + +static inline SDL_bool +DesktopIsFractionalScaled(SDL_Window *window) +{ + SDL_WindowData *data = window->driverdata; + SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata; + + if ((output->native_width != (output->width * data->scale_factor) || + output->native_height != (output->height * data->scale_factor))) { + return SDL_TRUE; + } + + return SDL_FALSE; +} + +static SDL_bool +NeedFullscreenViewport(SDL_Window *window) +{ + SDL_WindowData *data = window->driverdata; + SDL_VideoData *video = data->waylandData; + SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata; + + int fs_width, fs_height; + + GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL); + + /* + * Fullscreen needs a viewport: + * - If the desktop uses fractional scaling + * - Fullscreen desktop was not requested OR the window is DPI aware + * + * - The desktop uses non-fractional scaling + * - Fullscreen desktop was NOT requested + * + * - The desktop is not scaled + * - A non-native fullscreen mode was explicitly set by the client + */ + if (video->viewporter != NULL && (window->flags & SDL_WINDOW_FULLSCREEN)) { + if (DesktopIsFractionalScaled(window)) { + if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP || + (window->flags & SDL_WINDOW_ALLOW_HIGHDPI)) { + return SDL_TRUE; + } + } else if (DesktopIsScaled(window)) { + if ((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) { + return SDL_TRUE; + } + } else if (fs_width != output->native_width && fs_height != output->native_height) { + return SDL_TRUE; + } + } + + return SDL_FALSE; +} + +static void +SetViewport(SDL_Window *window, int src_width, int src_height, int dst_width, int dst_height) +{ + SDL_WindowData *wind = window->driverdata; + SDL_VideoData *video = wind->waylandData; + + if (video->viewporter) { + if (wind->viewport == NULL) { + wind->viewport = wp_viewporter_get_viewport(video->viewporter, wind->surface); + } + + wp_viewport_set_source(wind->viewport, wl_fixed_from_int(0), wl_fixed_from_int(0), wl_fixed_from_int(src_width), wl_fixed_from_int(src_height)); + wp_viewport_set_destination(wind->viewport, dst_width, dst_height); + } +} + +static void +UnsetViewport(SDL_Window *window) +{ + SDL_WindowData *wind = window->driverdata; + + if (wind->viewport) { + wp_viewport_destroy(wind->viewport); + wind->viewport = NULL; + } +} + +static void +ConfigureViewport(SDL_Window *window) +{ + SDL_WindowData *data = window->driverdata; + SDL_VideoData *viddata = data->waylandData; + SDL_WaylandOutputData *output = (SDL_WaylandOutputData *)SDL_GetDisplayForWindow(window)->driverdata; + + if ((window->flags & SDL_WINDOW_FULLSCREEN) && NeedFullscreenViewport(window)) { + int fs_width, fs_height; + int src_width, src_height; + + GetFullScreenDimensions(window, &fs_width, &fs_height, &src_width, &src_height); + SetViewport(window, src_width, src_height, output->width, output->height); + + data->pointer_scale = (float)fs_width / (float)output->width; + + /* + * If mouse_rect is not empty, re-create the confinement region with the new scale value. + * If the pointer is locked to the general surface with unspecified coordinates, it will + * be confined to the viewport region, so no update is required. + */ + if (!SDL_RectEmpty(&window->mouse_rect)) { + Wayland_input_confine_pointer(viddata->input, window); + } + } else { + UnsetViewport(window); + data->pointer_scale = 1.0f; + + /* Re-scale the pointer confinement region */ + if (!SDL_RectEmpty(&window->mouse_rect)) { + Wayland_input_confine_pointer(viddata->input, window); + } + } +} + +static void +SetDrawScale(SDL_Window *window) +{ + SDL_WindowData *data = window->driverdata; + + if ((window->flags & SDL_WINDOW_FULLSCREEN) && NeedFullscreenViewport(window)) { + int fs_width, fs_height; + + GetFullScreenDimensions(window, &fs_width, &fs_height, &data->drawable_width, &data->drawable_height); + + /* Set the buffer scale to 1 since a viewport will be used. */ + wl_surface_set_buffer_scale(data->surface, 1); + } else { + data->drawable_width = window->w * data->scale_factor; + data->drawable_height = window->h * data->scale_factor; + + wl_surface_set_buffer_scale(data->surface, (int32_t)data->scale_factor); + } +} + static void SetMinMaxDimensions(SDL_Window *window, SDL_bool commit) { @@ -303,9 +507,14 @@ handle_configure_xdg_toplevel(void *data, * UPDATE: Nope, sure enough a compositor sends 0,0. This is a known bug: * https://bugs.kde.org/show_bug.cgi?id=444962 */ - if (width != 0 && height != 0 && (window->w != width || window->h != height)) { - window->w = width; - window->h = height; + if (!NeedFullscreenViewport(window)) { + if (width != 0 && height != 0 && (window->w != width || window->h != height)) { + window->w = width; + window->h = height; + wind->needs_resize_event = SDL_TRUE; + } + } else { + GetFullScreenDimensions(window, &window->w, &window->h, NULL, NULL); wind->needs_resize_event = SDL_TRUE; } @@ -399,17 +608,23 @@ decoration_frame_configure(struct libdecor_frame *frame, * Always assume the configure is wrong. */ if (fullscreen) { - /* FIXME: We have been explicitly told to respect the fullscreen size - * parameters here, even though they are known to be wrong on GNOME at - * bare minimum. If this is wrong, don't blame us, we were explicitly - * told to do this. - */ - if (!libdecor_configuration_get_content_size(configuration, frame, - &width, &height)) { - width = window->w; - height = window->h; + if (!NeedFullscreenViewport(window)) { + /* FIXME: We have been explicitly told to respect the fullscreen size + * parameters here, even though they are known to be wrong on GNOME at + * bare minimum. If this is wrong, don't blame us, we were explicitly + * told to do this. + */ + if (!libdecor_configuration_get_content_size(configuration, frame, + &width, &height)) { + width = window->w; + height = window->h; + } + } else { + GetFullScreenDimensions(window, &width, &height, NULL, NULL); } + wind->needs_resize_event = SDL_TRUE; + /* This part is good though. */ if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { scale_factor = driverdata->scale_factor; @@ -1269,7 +1484,8 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window) data->waylandData = c; data->sdlwindow = window; - data->scale_factor = 1.0; + data->scale_factor = 1.0f; + data->pointer_scale = 1.0f; if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { int i; @@ -1316,9 +1532,11 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window) } #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */ + data->drawable_width = window->w * data->scale_factor; + data->drawable_height = window->h * data->scale_factor; + if (window->flags & SDL_WINDOW_OPENGL) { - data->egl_window = WAYLAND_wl_egl_window_create(data->surface, - window->w * data->scale_factor, window->h * data->scale_factor); + data->egl_window = WAYLAND_wl_egl_window_create(data->surface, data->drawable_width, data->drawable_height); #if SDL_VIDEO_OPENGL_EGL /* Create the GLES window surface */ @@ -1375,12 +1593,13 @@ Wayland_HandleResize(SDL_Window *window, int width, int height, float scale) data->needs_resize_event = SDL_FALSE; } - wl_surface_set_buffer_scale(data->surface, data->scale_factor); + /* Configure the backbuffer size and scale factors */ + SetDrawScale(window); if (data->egl_window) { WAYLAND_wl_egl_window_resize(data->egl_window, - window->w * data->scale_factor, - window->h * data->scale_factor, + data->drawable_width, + data->drawable_height, 0, 0); } @@ -1397,6 +1616,9 @@ Wayland_HandleResize(SDL_Window *window, int width, int height, float scale) if (viddata->shell.xdg && data->shell_surface.xdg.surface) { xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, window->w, window->h); } + + /* Update the viewport */ + ConfigureViewport(window); } void @@ -1431,12 +1653,12 @@ void Wayland_SetWindowSize(_THIS, SDL_Window * window) } #endif - wl_surface_set_buffer_scale(wind->surface, wind->scale_factor); + SetDrawScale(window); if (wind->egl_window) { WAYLAND_wl_egl_window_resize(wind->egl_window, - window->w * wind->scale_factor, - window->h * wind->scale_factor, + wind->drawable_width, + wind->drawable_height, 0, 0); } @@ -1549,6 +1771,10 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window) xdg_activation_token_v1_destroy(wind->activation_token); } + if (wind->viewport) { + wp_viewport_destroy(wind->viewport); + } + SDL_free(wind->outputs); if (wind->frame_callback) { diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 90e4d8cf6..8d53120a3 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -72,6 +72,7 @@ typedef struct { struct zwp_keyboard_shortcuts_inhibitor_v1 *key_inhibitor; struct zwp_idle_inhibitor_v1 *idle_inhibitor; struct xdg_activation_token_v1 *activation_token; + struct wp_viewport *viewport; /* floating dimensions for restoring from maximized and fullscreen */ int floating_width, floating_height; @@ -86,6 +87,8 @@ typedef struct { int num_outputs; float scale_factor; + float pointer_scale; + int drawable_width, drawable_height; SDL_bool needs_resize_event; SDL_bool floating_resize_pending; } SDL_WindowData; @@ -112,6 +115,7 @@ extern int Wayland_SetWindowModalFor(_THIS, SDL_Window * modal_window, SDL_Windo extern void Wayland_SetWindowTitle(_THIS, SDL_Window * window); extern void Wayland_DestroyWindow(_THIS, SDL_Window *window); extern void Wayland_SuspendScreenSaver(_THIS); +extern int Wayland_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode); extern SDL_bool Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info); diff --git a/wayland-protocols/viewporter.xml b/wayland-protocols/viewporter.xml new file mode 100644 index 000000000..c732d8c35 --- /dev/null +++ b/wayland-protocols/viewporter.xml @@ -0,0 +1,186 @@ + + + + + Copyright © 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + The x, y arguments of wl_surface.attach are applied as normal to + the surface. They indicate how many pixels to remove from the + surface size from the left and the top. In other words, they are + still in the surface-local coordinate system, just like dst_width + and dst_height are. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + +