From 0dae35bf3dd574c183afd561fb301ceaf33f51b9 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Sat, 19 Mar 2022 15:58:47 -0400 Subject: [PATCH] video: wayland: Use xdg-output for retrieving the desktop dimensions Using wl-output to get the desktop display dimensions and dividing by the integer scale factor will not return the correct result when using a desktop with fractional scaling (e.g. a 3840x2160 display at 150% will incorrectly report the scaled desktop area as 1920x1080 instead of 2560x1440). Use the xdg-output protocol, if available, to retrieve the correct desktop dimensions and offset. Versions 1 through 3 of the protocol are supported. --- src/video/wayland/SDL_waylandvideo.c | 155 +++++++++++-- src/video/wayland/SDL_waylandvideo.h | 6 +- wayland-protocols/xdg-output-unstable-v1.xml | 220 +++++++++++++++++++ 3 files changed, 366 insertions(+), 15 deletions(-) create mode 100644 wayland-protocols/xdg-output-unstable-v1.xml diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index b8f9a5997..9ceb9aa25 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -53,6 +53,7 @@ #include "xdg-activation-v1-client-protocol.h" #include "text-input-unstable-v3-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" +#include "xdg-output-unstable-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -60,6 +61,9 @@ #define WAYLANDVID_DRIVER_NAME "wayland" +static void +display_handle_done(void *data, struct wl_output *output); + /* Initialization/Query functions */ static int Wayland_VideoInit(_THIS); @@ -282,6 +286,60 @@ VideoBootStrap Wayland_bootstrap = { Wayland_CreateDevice }; +static void +xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, + int32_t x, int32_t y) +{ + SDL_WaylandOutputData* driverdata = data; + + driverdata->x = x; + driverdata->y = y; +} + +static void +xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, + int32_t width, int32_t height) +{ + SDL_WaylandOutputData* driverdata = data; + + driverdata->width = width; + driverdata->height = height; +} + +static void +xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) +{ + SDL_WaylandOutputData* driverdata = data; + + /* + * xdg-output.done events are deprecated and only apply below version 3 of the protocol. + * A wl-output.done event will be emitted in version 3 or higher. + */ + if (zxdg_output_v1_get_version(driverdata->xdg_output) < 3) { + display_handle_done(data, driverdata->output); + } +} + +static void +xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, + const char *name) +{ +} + +static void +xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, + const char *description) +{ +} + +static const struct zxdg_output_v1_listener xdg_output_listener = { + xdg_output_handle_logical_position, + xdg_output_handle_logical_size, + xdg_output_handle_done, + xdg_output_handle_name, + xdg_output_handle_description, +}; + static void display_handle_geometry(void *data, struct wl_output *output, @@ -298,7 +356,7 @@ display_handle_geometry(void *data, SDL_VideoDisplay *display; int i; - if (driverdata->done) { + if (driverdata->wl_output_done_count) { /* Clear the wl_output ref so Reset doesn't free it */ display = SDL_GetDisplay(driverdata->index); for (i = 0; i < display->num_display_modes; i += 1) { @@ -309,11 +367,14 @@ display_handle_geometry(void *data, SDL_ResetDisplayModes(driverdata->index); /* The display has officially started over. */ - driverdata->done = SDL_FALSE; + driverdata->wl_output_done_count = 0; } - driverdata->x = x; - driverdata->y = y; + /* Apply the change from wl-output only if xdg-output is not supported */ + if (driverdata->xdg_output) { + driverdata->x = x; + driverdata->y = y; + } driverdata->physical_width = physical_width; driverdata->physical_height = physical_height; if (driverdata->index == -1) { @@ -363,9 +424,15 @@ display_handle_mode(void *data, SDL_DisplayMode mode; if (flags & WL_OUTPUT_MODE_CURRENT) { - /* Don't rotate this yet, handle_done will do it later */ - driverdata->width = width; - driverdata->height = height; + /* + * Don't rotate this yet, wl-output coordinates are transformed in + * handle_done and xdg-output coordinates are pre-transformed. + */ + if (!driverdata->xdg_output) { + driverdata->width = width; + driverdata->height = height; + } + driverdata->refresh = refresh; } @@ -401,17 +468,37 @@ display_handle_done(void *data, SDL_DisplayMode mode; SDL_VideoDisplay *dpy; - if (driverdata->done) - return; + /* + * When using xdg-output, two wl-output.done events will be emitted: + * one at the completion of wl-display and one at the completion of xdg-output. + * + * All required events must be received before proceeding. + */ + const int event_await_count = 1 + (driverdata->xdg_output != NULL); - driverdata->done = SDL_TRUE; + driverdata->wl_output_done_count = SDL_min(driverdata->wl_output_done_count + 1, event_await_count + 1); + + if (driverdata->wl_output_done_count != event_await_count) { + return; + } SDL_zero(mode); mode.format = SDL_PIXELFORMAT_RGB888; - if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { + + if (driverdata->xdg_output) { + /* 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; + } else { + mode.w = driverdata->width / driverdata->scale_factor; + mode.h = driverdata->height / driverdata->scale_factor; + } + /* Calculate the display DPI */ + if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { driverdata->hdpi = driverdata->physical_height ? (((float) driverdata->height) * 25.4f / driverdata->physical_height) : 0.0f; @@ -423,9 +510,6 @@ display_handle_done(void *data, ((float) driverdata->physical_height) / 25.4f, ((float) driverdata->physical_width) / 25.4f); } else { - mode.w = driverdata->width / driverdata->scale_factor; - mode.h = driverdata->height / driverdata->scale_factor; - driverdata->hdpi = driverdata->physical_width ? (((float) driverdata->width) * 25.4f / driverdata->physical_width) : 0.0f; @@ -437,6 +521,7 @@ display_handle_done(void *data, ((float) driverdata->physical_width) / 25.4f, ((float) driverdata->physical_height) / 25.4f); } + mode.refresh_rate = (int)SDL_round(driverdata->refresh / 1000.0); /* mHz to Hz */ mode.driverdata = driverdata->output; @@ -500,6 +585,24 @@ Wayland_add_display(SDL_VideoData *d, uint32_t id) wl_output_add_listener(output, &output_listener, data); SDL_WAYLAND_register_output(output); + + /* Keep a list of outputs for deferred xdg-output initialization. */ + if (d->output_list != NULL) { + SDL_WaylandOutputData *node = (SDL_WaylandOutputData*)d->output_list; + + while (node->next != NULL) { + node = (SDL_WaylandOutputData*)node->next; + } + + node->next = (struct SDL_WaylandOutputData*)data; + } else { + d->output_list = (struct SDL_WaylandOutputData*)data; + } + + if (data->videodata->xdg_output_manager) { + data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output); + zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data); + } } static void @@ -515,6 +618,9 @@ Wayland_free_display(uint32_t id) data = (SDL_WaylandOutputData *) display->driverdata; if (data->registry_id == id) { SDL_DelVideoDisplay(i); + if (data->xdg_output) { + zxdg_output_v1_destroy(data->xdg_output); + } wl_output_destroy(data->output); SDL_free(data); @@ -531,6 +637,15 @@ Wayland_free_display(uint32_t id) } } +static void +Wayland_init_xdg_output(SDL_VideoData *d) +{ + for (SDL_WaylandOutputData *node = (SDL_WaylandOutputData*)d->output_list; node != NULL; node = (SDL_WaylandOutputData*)node->next) { + node->xdg_output = zxdg_output_manager_v1_get_xdg_output(node->videodata->xdg_output_manager, node->output); + zxdg_output_v1_add_listener(node->xdg_output, &xdg_output_listener, node); + } +} + #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH static void windowmanager_hints(void *data, struct qt_windowmanager *qt_windowmanager, @@ -616,6 +731,10 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id, if (d->input) { Wayland_input_add_tablet(d->input, d->tablet_manager); } + } else if (SDL_strcmp(interface, "zxdg_output_manager_v1") == 0) { + 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); #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH } else if (SDL_strcmp(interface, "qt_touch_extension") == 0) { @@ -733,6 +852,10 @@ Wayland_VideoQuit(_THIS) for (i = 0; i < _this->num_displays; ++i) { SDL_VideoDisplay *display = &_this->displays[i]; + if (((SDL_WaylandOutputData*)display->driverdata)->xdg_output) { + zxdg_output_v1_destroy(((SDL_WaylandOutputData*)display->driverdata)->xdg_output); + } + wl_output_destroy(((SDL_WaylandOutputData*)display->driverdata)->output); SDL_free(display->driverdata); display->driverdata = NULL; @@ -797,6 +920,10 @@ Wayland_VideoQuit(_THIS) } #endif + if (data->xdg_output_manager) { + zxdg_output_manager_v1_destroy(data->xdg_output_manager); + } + 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 5c658a3c2..cab8aca95 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -71,6 +71,7 @@ typedef struct { struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager; struct xdg_activation_v1 *activation_manager; struct zwp_text_input_manager_v3 *text_input_manager; + struct zxdg_output_manager_v1 *xdg_output_manager; EGLDisplay edpy; EGLContext context; @@ -79,6 +80,7 @@ typedef struct { struct xkb_context *xkb_context; struct SDL_WaylandInput *input; struct SDL_WaylandTabletManager *tablet_manager; + struct SDL_WaylandOutputData *output_list; #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH struct SDL_WaylandTouch *touch; @@ -94,6 +96,7 @@ typedef struct { typedef struct { SDL_VideoData *videodata; struct wl_output *output; + struct zxdg_output_v1 *xdg_output; uint32_t registry_id; float scale_factor; int x, y, width, height, refresh, transform; @@ -102,7 +105,8 @@ typedef struct { float ddpi, hdpi, vdpi; int index; SDL_VideoDisplay placeholder; - SDL_bool done; + int wl_output_done_count; + struct SDL_WaylandOutputData *next; } SDL_WaylandOutputData; /* Needed here to get wl_surface declaration, fixes GitHub#4594 */ diff --git a/wayland-protocols/xdg-output-unstable-v1.xml b/wayland-protocols/xdg-output-unstable-v1.xml new file mode 100644 index 000000000..9a5b79000 --- /dev/null +++ b/wayland-protocols/xdg-output-unstable-v1.xml @@ -0,0 +1,220 @@ + + + + + Copyright © 2017 Red Hat Inc. + + 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. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + Some of the information provided in this protocol might be identical + to their counterparts already available from wl_output, in which case + the information provided by this protocol should be preferred to their + equivalent in wl_output. The goal is to move the desktop specific + concepts (such as output location within the global compositor space, + the connector name and types, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + For objects version 3 onwards, after all xdg_output properties have been + sent (when the object is created and when properties are updated), a + wl_output.done event is sent. This allows changes to the output + properties to be seen as atomic, even if they happen via multiple events. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + For example, a surface without any buffer scale, transformation + nor rotation set, with the size matching the logical_size will + have the same size as the corresponding output when displayed. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the surface buffers will advertise a + logical size of 3840×2160, + + - A compositor automatically scaling the surface buffers will + advertise a logical size of 1920×1080, + + - A compositor using a fractional scale of 1.5 will advertise a + logical size of 2560×1440. + + For example, for a wl_output mode 1920×1080 and a 90 degree rotation, + the compositor will advertise a logical size of 1080x1920. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + For objects version 3 onwards, this event is deprecated. Compositors + are not required to send it anymore and must send wl_output.done + instead. + + + + + + + + Many compositors will assign names to their outputs, show them to the + user, allow them to be configured by name, etc. The client may wish to + know this name as well to offer the user similar behaviors. + + The naming convention is compositor defined, but limited to + alphanumeric characters and dashes (-). Each name is unique among all + wl_output globals, but if a wl_output global is destroyed the same name + may be reused later. The names will also remain consistent across + sessions with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + The name event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output). This event is only sent once per + xdg_output, and the name does not change over the lifetime of the + wl_output global. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, to + communicate the user for various purposes. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. + + The description event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output) and whenever the description + changes. The description is optional, and may not be sent at all. + + For objects of version 2 and lower, this event is only sent once per + xdg_output, and the description does not change over the lifetime of + the wl_output global. + + + + + +