From b4b1cdeb27fa7b3beb1181de38e8913f93772173 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 24 Jun 2026 10:20:39 -0400
Subject: [PATCH] wayland: Add support for the toplevel tag protocol
Uses the same ID string as the session manager protocol.
---
src/video/wayland/SDL_waylandvideo.c | 8 +++
src/video/wayland/SDL_waylandvideo.h | 1 +
src/video/wayland/SDL_waylandwindow.c | 19 +++--
wayland-protocols/xdg-toplevel-tag-v1.xml | 85 +++++++++++++++++++++++
4 files changed, 106 insertions(+), 7 deletions(-)
create mode 100644 wayland-protocols/xdg-toplevel-tag-v1.xml
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index a912b1d96d8ea..9404947f5ef3d 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -71,6 +71,7 @@
#include "pointer-gestures-unstable-v1-client-protocol.h"
#include "single-pixel-buffer-v1-client-protocol.h"
#include "xdg-session-management-v1-client-protocol.h"
+#include "xdg-toplevel-tag-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -1511,6 +1512,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
d->single_pixel_buffer_manager = wl_registry_bind(d->registry, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
} else if (SDL_strcmp(interface, xdg_session_manager_v1_interface.name) == 0) {
d->xdg_session_manager = wl_registry_bind(d->registry, id, &xdg_session_manager_v1_interface, 1);
+ } else if (SDL_strcmp(interface, xdg_toplevel_tag_manager_v1_interface.name) == 0) {
+ d->xdg_toplevel_tag_manager = wl_registry_bind(d->registry, id, &xdg_toplevel_tag_manager_v1_interface, 1);
}
#ifdef SDL_WL_FIXES_VERSION
else if (SDL_strcmp(interface, wl_fixes_interface.name) == 0) {
@@ -1935,6 +1938,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->xdg_session_manager = NULL;
}
+ if (data->xdg_toplevel_tag_manager) {
+ xdg_toplevel_tag_manager_v1_destroy(data->xdg_toplevel_tag_manager);
+ data->xdg_toplevel_tag_manager = NULL;
+ }
+
if (data->subcompositor) {
wl_subcompositor_destroy(data->subcompositor);
data->subcompositor = NULL;
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 6be3f3835f0e8..f06895aa40a93 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -88,6 +88,7 @@ struct SDL_VideoData
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager;
struct xdg_session_manager_v1 *xdg_session_manager;
+ struct xdg_toplevel_tag_manager_v1 *xdg_toplevel_tag_manager;
struct xdg_session_v1 *xdg_session;
struct xkb_context *xkb_context;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 6fad2002e0bc0..f5c7b4bb9a2dc 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -50,6 +50,7 @@
#include "xdg-toplevel-icon-v1-client-protocol.h"
#include "color-management-v1-client-protocol.h"
#include "xdg-session-management-v1-client-protocol.h"
+#include "xdg-toplevel-tag-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -2053,22 +2054,26 @@ bool Wayland_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool mod
static void Wayland_RegisterToplevelForSession(SDL_WindowData *data)
{
- SDL_VideoDevice *viddev = SDL_GetVideoDevice();
- SDL_VideoData *viddata = viddev->internal;
-
struct xdg_toplevel *toplevel = GetToplevelForWindow(data);
if (!toplevel) {
return;
}
- if (viddata->xdg_session_manager) {
- const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
- if (id && *id != '\0') {
+ const char *id = SDL_GetStringProperty(data->sdlwindow->props, SDL_PROP_WINDOW_WAYLAND_WINDOW_ID_STRING, NULL);
+ if (id && *id != '\0') {
+ SDL_VideoDevice *viddev = SDL_GetVideoDevice();
+ SDL_VideoData *viddata = viddev->internal;
+
+ if (viddata->xdg_toplevel_tag_manager) {
+ xdg_toplevel_tag_manager_v1_set_toplevel_tag(viddata->xdg_toplevel_tag_manager, toplevel, id);
+ }
+
+ if (viddata->xdg_session_manager) {
Wayland_CreateSession(viddata);
if (viddata->xdg_session) {
CHECK_PARAM (true) {
- // Windows must not have a duplicate ID string, or a protocol error will result.
+ // Windows added to a session must not have a duplicate ID string, or a protocol error will result.
for (SDL_Window *w = viddev->windows; w; w = w->next) {
SDL_WindowData *d = w->internal;
if (d->session_id && SDL_strcmp(d->session_id, id) == 0) {
diff --git a/wayland-protocols/xdg-toplevel-tag-v1.xml b/wayland-protocols/xdg-toplevel-tag-v1.xml
new file mode 100644
index 0000000000000..5e20bdf45f816
--- /dev/null
+++ b/wayland-protocols/xdg-toplevel-tag-v1.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_toplevel_tag_v1">
+ <copyright>
+ Copyright © 2024 Xaver Hugl
+
+ 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.
+ </copyright>
+
+ <interface name="xdg_toplevel_tag_manager_v1" version="1">
+ <description summary="protocol for setting toplevel tags">
+ In order to make some window properties like position, size,
+ "always on top" or user defined rules for window behavior persistent, the
+ compositor needs some way to identify windows even after the application
+ has been restarted.
+ This protocol allows clients to make this possible by setting a tag for
+ toplevels.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy toplevel tag object">
+ Destroy this toplevel tag manager object. This request has no other
+ effects.
+ </description>
+ </request>
+
+ <request name="set_toplevel_tag">
+ <description summary="set tag">
+ Set a tag for a toplevel. The tag may be shown to the user in UI, so
+ it's preferable for it to be human readable, but it must be suitable
+ for configuration files and should not be translated.
+ Suitable tags would for example be "main window", "settings",
+ "e-mail composer" or similar.
+
+ The tag does not need to be unique across applications, and the client
+ may set the same tag for multiple windows, for example if the user has
+ opened the same UI twice. How the potentially resulting conflicts are
+ handled is compositor policy.
+
+ The client should set the tag as part of the initial commit on the
+ associated toplevel, but it may set it at any time afterwards as well,
+ for example if the purpose of the toplevel changes.
+ </description>
+ <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+ <arg name="tag" type="string" summary="untranslated tag"/>
+ </request>
+
+ <request name="set_toplevel_description">
+ <description summary="set description">
+ Set a description for a toplevel. This description may be shown to the
+ user in UI or read by a screen reader for accessibility purposes, and
+ should be translated.
+ It is recommended to make the description the translation of the tag.
+
+ The client should set the description as part of the initial commit on
+ the associated toplevel, but it may set it at any time afterwards as
+ well, for example if the purpose of the toplevel changes.
+ </description>
+ <arg name="toplevel" type="object" interface="xdg_toplevel"/>
+ <arg name="description" type="string" summary="translated description"/>
+ </request>
+ </interface>
+
+</protocol>