SDL: wayland: Don't defer clipboard source submission when no serial is available

From 4a17e772ea9a4ff77dd10fa6ea6f45fc6731eb01 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 30 Sep 2026 15:02:18 -0400
Subject: [PATCH] wayland: Don't defer clipboard source submission when no
 serial is available

This can cause a race condition, as an old source may end up overwriting a more current offer, depending on the exact order of events sent by the compositor. Submit it when the client asks, and allow the compositor to decide whether to accept it.

Some compositors also accept keyboard focus serials as valid for setting the clipboard, so keyboard focus events should set the implicit grab serial as well.
---
 src/video/wayland/SDL_waylanddatamanager.c | 40 +++-------------------
 src/video/wayland/SDL_waylanddatamanager.h |  4 ---
 src/video/wayland/SDL_waylandevents.c      |  4 +--
 src/video/wayland/SDL_waylandevents_c.h    |  3 +-
 4 files changed, 8 insertions(+), 43 deletions(-)

diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c
index 2a81624e0dcf9..ce6964caa0d87 100644
--- a/src/video/wayland/SDL_waylanddatamanager.c
+++ b/src/video/wayland/SDL_waylanddatamanager.c
@@ -762,10 +762,7 @@ bool Wayland_DataDeviceSetSelectionSource(SDL_WaylandDataDevice *data_device, SD
         // Advertise the data origin MIME
         wl_data_source_offer(source->source, SDL_DATA_ORIGIN_MIME);
 
-        // Only set if there is a valid serial if not set it later
-        if (data_device->selection_serial != 0) {
-            wl_data_device_set_selection(data_device->data_device, source->source, data_device->selection_serial);
-        }
+        wl_data_device_set_selection(data_device->data_device, source->source, data_device->seat->last_implicit_grab_serial);
         if (data_device->selection_source) {
             Wayland_DataSourceDestroy(data_device->selection_source);
         }
@@ -798,12 +795,9 @@ bool Wayland_PrimarySelectionDeviceSetSelection(SDL_WaylandPrimarySelectionDevic
             zwp_primary_selection_source_v1_offer(source->source, mime_type);
         }
 
-        // Only set if there is a valid serial if not set it later
-        if (primary_selection_device->selection_serial != 0) {
-            zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
-                                                          source->source,
-                                                          primary_selection_device->selection_serial);
-        }
+        zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
+                                                      source->source,
+                                                      primary_selection_device->seat->last_implicit_grab_serial);
         if (primary_selection_device->selection_source) {
             Wayland_PrimarySelectionSourceDestroy(primary_selection_device->selection_source);
         }
@@ -818,30 +812,4 @@ bool Wayland_PrimarySelectionDeviceSetSelection(SDL_WaylandPrimarySelectionDevic
     return true;
 }
 
-void Wayland_DataDeviceSetSerial(SDL_WaylandDataDevice *data_device, uint32_t serial)
-{
-    if (data_device) {
-        // If there was no serial and there is a pending selection, set it now.
-        if (data_device->selection_serial == 0 && data_device->selection_source) {
-            wl_data_device_set_selection(data_device->data_device, data_device->selection_source->source, serial);
-        }
-
-        data_device->selection_serial = serial;
-    }
-}
-
-void Wayland_PrimarySelectionDeviceSetSerial(SDL_WaylandPrimarySelectionDevice *primary_selection_device, uint32_t serial)
-{
-    if (primary_selection_device) {
-        // If there was no serial and there is a pending selection, set it now.
-        if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) {
-            zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
-                                                          primary_selection_device->selection_source->source,
-                                                          serial);
-        }
-
-        primary_selection_device->selection_serial = serial;
-    }
-}
-
 #endif // SDL_VIDEO_DRIVER_WAYLAND
diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h
index aeba94d86626d..fe9919f449829 100644
--- a/src/video/wayland/SDL_waylanddatamanager.h
+++ b/src/video/wayland/SDL_waylanddatamanager.h
@@ -100,7 +100,6 @@ struct SDL_WaylandDataDevice
     struct wl_surface *dnd_surface;
 
     // Clipboard and Primary Selection
-    uint32_t selection_serial;
     SDL_WaylandDataSource *selection_source;
 };
 
@@ -109,7 +108,6 @@ struct SDL_WaylandPrimarySelectionDevice
     struct zwp_primary_selection_device_v1 *primary_selection_device;
     struct SDL_WaylandSeat *seat;
 
-    uint32_t selection_serial;
     SDL_WaylandPrimarySelectionSource *selection_source;
     SDL_WaylandPrimarySelectionOffer *selection_offer;
 };
@@ -140,7 +138,5 @@ extern void Wayland_PrimarySelectionOfferDestroy(SDL_WaylandPrimarySelectionOffe
 // Clipboard / Primary Selection
 extern bool Wayland_DataDeviceSetSelectionSource(SDL_WaylandDataDevice *device, SDL_WaylandDataSource *source, const char **mime_types, size_t mime_count);
 extern bool Wayland_PrimarySelectionDeviceSetSelection(SDL_WaylandPrimarySelectionDevice *device, SDL_WaylandPrimarySelectionSource *source, const char *const *mime_types, size_t mime_count);
-extern void Wayland_DataDeviceSetSerial(SDL_WaylandDataDevice *device, uint32_t serial);
-extern void Wayland_PrimarySelectionDeviceSetSerial(SDL_WaylandPrimarySelectionDevice *device, uint32_t serial);
 
 #endif // SDL_waylanddatamanager_h_
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index d2414233fa85c..d043e74138b04 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -2115,6 +2115,8 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
         return;
     }
 
+    Wayland_UpdateImplicitGrabSerial(seat, serial);
+
     SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
     if (!window) {
         // Not a surface owned by SDL.
@@ -3912,8 +3914,6 @@ void Wayland_UpdateImplicitGrabSerial(SDL_WaylandSeat *seat, Uint32 serial)
     if (serial > seat->last_implicit_grab_serial) {
         seat->last_implicit_grab_serial = serial;
         seat->display->last_implicit_grab_seat = seat;
-        Wayland_DataDeviceSetSerial(seat->data_device, serial);
-        Wayland_PrimarySelectionDeviceSetSerial(seat->primary_selection_device, serial);
     }
 }
 
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 703a5fb140f09..4e0a7910e338d 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -296,12 +296,13 @@ extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_Windo
 extern void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window);
 
 /* The implicit grab serial needs to be updated on:
+ * - Gaining keyboard focus
  * - Keyboard key down/up
  * - Mouse button down
  * - Touch event down
  * - Tablet tool down
  * - Tablet tool button down/up
  */
-extern void Wayland_UpdateImplicitGrabSerial(struct SDL_WaylandSeat *seat, Uint32 serial);
+extern void Wayland_UpdateImplicitGrabSerial(SDL_WaylandSeat *seat, Uint32 serial);
 
 #endif // SDL_waylandevents_h_