wayland: Use the preferred order of displays exposed by KDE

KDE provides the kde_output_order_v1 protocol, which tells clients the preferred order of all connected displays. Sort SDL displays according to the provided list at init time.
This commit is contained in:
Frank Praznik 2024-01-26 18:15:27 -05:00
parent e0c2cca629
commit e71e16950a
3 changed files with 155 additions and 17 deletions

View File

@ -57,6 +57,7 @@
#include "input-timestamps-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
#include "pointer-constraints-unstable-v1-client-protocol.h"
#include "kde-output-order-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@ -96,7 +97,7 @@
/* GNOME doesn't expose displays in any particular order, but we can find the
* primary display and its logical coordinates via a DBus method.
*/
static SDL_bool Wayland_GetPrimaryDisplayCoordinates(int *x, int *y)
static SDL_bool Wayland_GetGNOMEPrimaryDisplayCoordinates(int *x, int *y)
{
#ifdef SDL_USE_LIBDBUS
SDL_DBusContext *dbus = SDL_DBus_GetContext();
@ -193,6 +194,97 @@ error:
return SDL_FALSE;
}
static void Wayland_FlushOutputOrder(SDL_VideoData *vid)
{
SDL_WaylandConnectorName *c, *tmp;
wl_list_for_each_safe (c, tmp, &vid->output_order, link) {
WAYLAND_wl_list_remove(&c->link);
SDL_free(c);
}
vid->output_order_finalized = SDL_FALSE;
}
/* The order of wl_output displays exposed by KDE doesn't correspond to any priority, but KDE does provide a protocol
* that tells clients the preferred order or all connected displays via an ordered list of connector name strings.
*/
static void handle_kde_output_order_output(void *data, struct kde_output_order_v1 *kde_output_order_v1, const char *output_name)
{
SDL_VideoData *vid = (SDL_VideoData *)data;
/* Starting a new list, flush the old. */
if (vid->output_order_finalized) {
Wayland_FlushOutputOrder(vid);
}
const int len = SDL_strlen(output_name) + 1;
SDL_WaylandConnectorName *node = SDL_malloc(sizeof(SDL_WaylandConnectorName) + len);
SDL_strlcpy(node->wl_output_name, output_name, len);
WAYLAND_wl_list_insert(vid->output_order.prev, &node->link);
}
static void handle_kde_output_order_done(void *data, struct kde_output_order_v1 *kde_output_order_v1)
{
SDL_VideoData *vid = (SDL_VideoData *)data;
vid->output_order_finalized = SDL_TRUE;
}
static const struct kde_output_order_v1_listener kde_output_order_listener = {
handle_kde_output_order_output,
handle_kde_output_order_done
};
static void Wayland_SortOutputs(SDL_VideoData *vid)
{
SDL_DisplayData *d;
int p_x, p_y;
/* KDE provides the kde-output-order-v1 protocol, which gives us the full preferred display
* ordering in the form of a list of wl_output.name strings (connector names).
*/
if (!WAYLAND_wl_list_empty(&vid->output_order)) {
struct wl_list sorted_list;
SDL_WaylandConnectorName *c;
/* Sort the outputs by connector name. */
WAYLAND_wl_list_init(&sorted_list);
wl_list_for_each (c, &vid->output_order, link) {
wl_list_for_each (d, &vid->output_list, link) {
if (SDL_strcmp(c->wl_output_name, d->wl_output_name) == 0) {
/* Remove from the current list and Append the next node to the end of the new list. */
WAYLAND_wl_list_remove(&d->link);
WAYLAND_wl_list_insert(sorted_list.prev, &d->link);
break;
}
}
}
if (!WAYLAND_wl_list_empty(&vid->output_list)) {
/* If any displays were omitted during the sort, append them to the new list.
* This shouldn't happen, but better safe than sorry.
*/
WAYLAND_wl_list_insert_list(sorted_list.prev, &vid->output_list);
}
/* Set the output list to the sorted list. */
WAYLAND_wl_list_init(&vid->output_list);
WAYLAND_wl_list_insert_list(&vid->output_list, &sorted_list);
} else if (Wayland_GetGNOMEPrimaryDisplayCoordinates(&p_x, &p_y)) {
/* GNOME doesn't expose the displays in any preferential order, so find the primary display coordinates and use them
* to manually sort the primary display to the front of the list so that it is always the first exposed by SDL.
* Otherwise, assume that the displays were already exposed in preferential order.
*/
wl_list_for_each (d, &vid->output_list, link) {
if (d->x == p_x && d->y == p_y) {
WAYLAND_wl_list_remove(&d->link);
WAYLAND_wl_list_insert(&vid->output_list, &d->link);
break;
}
}
}
}
static void display_handle_done(void *data, struct wl_output *output);
/* Initialization/Query functions */
@ -326,6 +418,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
data->input = input;
data->display_externally_owned = display_is_external;
WAYLAND_wl_list_init(&data->output_list);
WAYLAND_wl_list_init(&data->output_order);
WAYLAND_wl_list_init(&external_window_list);
/* Initialize all variables that we clean on shutdown */
@ -803,6 +896,10 @@ static void display_handle_scale(void *data,
static void display_handle_name(void *data, struct wl_output *wl_output, const char *name)
{
SDL_DisplayData *driverdata = (SDL_DisplayData *)data;
SDL_free(driverdata->wl_output_name);
driverdata->wl_output_name = SDL_strdup(name);
}
static void display_handle_description(void *data, struct wl_output *wl_output, const char *description)
@ -859,6 +956,8 @@ static void Wayland_free_display(SDL_VideoDisplay *display)
SDL_DisplayData *display_data = display->driverdata;
int i;
SDL_free(display_data->wl_output_name);
if (display_data->xdg_output) {
zxdg_output_v1_destroy(display_data->xdg_output);
}
@ -884,23 +983,9 @@ static void Wayland_free_display(SDL_VideoDisplay *display)
static void Wayland_FinalizeDisplays(SDL_VideoData *vid)
{
SDL_DisplayData *d;
int p_x, p_y;
/* GNOME doesn't expose the displays in any preferential order, so find the primary display coordinates and use them
* to manually sort the primary display to the front of the list so that it is always the first exposed by SDL.
* Otherwise, assume that the displays were already exposed in preferential order.
* */
if (Wayland_GetPrimaryDisplayCoordinates(&p_x, &p_y)) {
wl_list_for_each (d, &vid->output_list, link) {
if (d->x == p_x && d->y == p_y) {
WAYLAND_wl_list_remove(&d->link);
WAYLAND_wl_list_insert(&vid->output_list, &d->link);
break;
}
}
}
wl_list_for_each (d, &vid->output_list, link) {
Wayland_SortOutputs(vid);
wl_list_for_each(d, &vid->output_list, link) {
d->display = SDL_AddVideoDisplay(&d->placeholder, SDL_FALSE);
SDL_free(d->placeholder.name);
SDL_zero(d->placeholder);
@ -994,6 +1079,9 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
if (d->input) {
Wayland_RegisterTimestampListeners(d->input);
}
} else if (SDL_strcmp(interface, "kde_output_order_v1") == 0) {
d->kde_output_order = wl_registry_bind(d->registry, id, &kde_output_order_v1_interface, 1);
kde_output_order_v1_add_listener(d->kde_output_order, &kde_output_order_listener, d);
}
}
@ -1223,6 +1311,12 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->input_timestamps_manager = NULL;
}
if (data->kde_output_order) {
Wayland_FlushOutputOrder(data);
kde_output_order_v1_destroy(data->kde_output_order);
data->kde_output_order = NULL;
}
if (data->compositor) {
wl_compositor_destroy(data->compositor);
data->compositor = NULL;

View File

@ -41,6 +41,12 @@ typedef struct
int size;
} SDL_WaylandCursorTheme;
typedef struct
{
struct wl_list link;
char wl_output_name[];
} SDL_WaylandConnectorName;
struct SDL_VideoData
{
SDL_bool initializing;
@ -72,11 +78,15 @@ struct SDL_VideoData
struct wp_viewporter *viewporter;
struct wp_fractional_scale_manager_v1 *fractional_scale_manager;
struct zwp_input_timestamps_manager_v1 *input_timestamps_manager;
struct kde_output_order_v1 *kde_output_order;
struct xkb_context *xkb_context;
struct SDL_WaylandInput *input;
struct SDL_WaylandTabletManager *tablet_manager;
struct wl_list output_list;
struct wl_list output_order;
SDL_bool output_order_finalized;
int relative_mouse_mode;
SDL_bool display_externally_owned;
@ -87,6 +97,7 @@ struct SDL_DisplayData
SDL_VideoData *videodata;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
char *wl_output_name;
uint32_t registry_id;
float scale_factor;
int pixel_width, pixel_height;

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="kde_output_order_v1">
<copyright><![CDATA[
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: MIT-CMU
]]></copyright>
<interface name="kde_output_order_v1" version="1">
<description summary="announce order of outputs">
Announce the order in which desktop environment components should be placed on outputs.
The compositor will send the list of outputs when the global is bound and whenever there is a change.
</description>
<event name="output">
<description summary="output name">
Specifies the output identified by their wl_output.name.
</description>
<arg name="output_name" type="string" summary="the name of the output"/>
</event>
<event name="done">
<description summary="done">
Specifies that the output list is complete. On the next output event, a new list begins.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="Destroy the output order notifier."/>
</request>
</interface>
</protocol>