SDL: Support SDL_EVENT_DROP_TEXT in Wayland

From e87647c35145cef973a860947e8d7c08fe598d9b Mon Sep 17 00:00:00 2001
From: Dragon-Baroque <[EMAIL REDACTED]>
Date: Wed, 6 Sep 2023 14:57:04 +0200
Subject: [PATCH] Support SDL_EVENT_DROP_TEXT in Wayland

  src/video/wayland/SDL_waylanddatamanager.c
    Log data + primary_selection _offer_receive
  src/video/wayland/SDL_waylandevents.c + SDL_waylanddatamanager.h
    Log data + primary_selection events
    Split FILE vs TEXT events : booleans has_mime_ text + file
    Handle text/plain;charset=utf-8 data offer
---
 src/video/wayland/SDL_waylanddatamanager.c |   6 +
 src/video/wayland/SDL_waylanddatamanager.h |   1 +
 src/video/wayland/SDL_waylandevents.c      | 157 ++++++++++++++++++---
 3 files changed, 144 insertions(+), 20 deletions(-)

diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c
index 0fab6fe21fa47..dd0b8aeb71995 100644
--- a/src/video/wayland/SDL_waylanddatamanager.c
+++ b/src/video/wayland/SDL_waylanddatamanager.c
@@ -381,6 +381,9 @@ void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
         }
         close(pipefd[0]);
     }
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In Wayland_data_offer_receive for '%s', buffer (%ld) at %p\n",
+                 mime_type, *length, buffer);
     return buffer;
 }
 
@@ -414,6 +417,9 @@ void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *
         }
         close(pipefd[0]);
     }
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In Wayland_primary_selection_offer_receive for '%s', buffer (%ld) at %p\n",
+                 mime_type, *length, buffer);
     return buffer;
 }
 
diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h
index 19fbc004eed6c..6b0f880adcf62 100644
--- a/src/video/wayland/SDL_waylanddatamanager.h
+++ b/src/video/wayland/SDL_waylanddatamanager.h
@@ -85,6 +85,7 @@ typedef struct
     uint32_t drag_serial;
     SDL_WaylandDataOffer *drag_offer;
     SDL_WaylandDataOffer *selection_offer;
+    SDL_bool has_mime_file, has_mime_text;
     SDL_Window *dnd_window;
 
     /* Clipboard and Primary Selection */
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 3ec8d90ce38c2..76e9856be3be2 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -1956,16 +1956,28 @@ static void data_offer_handle_offer(void *data, struct wl_data_offer *wl_data_of
 {
     SDL_WaylandDataOffer *offer = data;
     Wayland_data_offer_add_mime(offer, mime_type);
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In wl_data_offer_listener . data_offer_handle_offer on data_offer 0x%08x for MIME '%s'\n",
+                 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) wl_data_offer) : -1),
+                 mime_type);
 }
 
 static void data_offer_handle_source_actions(void *data, struct wl_data_offer *wl_data_offer,
                                              uint32_t source_actions)
 {
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In wl_data_offer_listener . data_offer_handle_source_actions on data_offer 0x%08x for Source Actions '%d'\n",
+                 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) wl_data_offer) : -1),
+                 source_actions);
 }
 
 static void data_offer_handle_actions(void *data, struct wl_data_offer *wl_data_offer,
                                       uint32_t dnd_action)
 {
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In wl_data_offer_listener . data_offer_handle_actions on data_offer 0x%08x for DND Actions '%d'\n",
+                 (wl_data_offer ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) wl_data_offer) : -1),
+                 dnd_action);
 }
 
 static const struct wl_data_offer_listener data_offer_listener = {
@@ -1979,6 +1991,10 @@ static void primary_selection_offer_handle_offer(void *data, struct zwp_primary_
 {
     SDL_WaylandPrimarySelectionOffer *offer = data;
     Wayland_primary_selection_offer_add_mime(offer, mime_type);
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In zwp_primary_selection_offer_v1_listener . primary_selection_offer_handle_offer on primary_selection_offer 0x%08x for MIME '%s'\n",
+                 (zwp_primary_selection_offer_v1 ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) zwp_primary_selection_offer_v1) : -1),
+                 mime_type);
 }
 
 static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
@@ -1995,6 +2011,9 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_
         WAYLAND_wl_list_init(&(data_offer->mimes));
         wl_data_offer_set_user_data(id, data_offer);
         wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_data_offer on data_offer 0x%08x\n",
+                     (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) id) : -1));
     }
 }
 
@@ -2003,7 +2022,8 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_
                                      wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id)
 {
     SDL_WaylandDataDevice *data_device = data;
-    SDL_bool has_mime = SDL_FALSE;
+    data_device->has_mime_file = SDL_FALSE;
+    data_device->has_mime_text = SDL_FALSE;
     uint32_t dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
 
     data_device->drag_serial = serial;
@@ -2014,17 +2034,23 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_
         /* TODO: SDL Support more mime types */
 #ifdef SDL_USE_LIBDBUS
         if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_PORTAL_MIME)) {
-            has_mime = SDL_TRUE;
+            data_device->has_mime_file = SDL_TRUE;
             wl_data_offer_accept(id, serial, FILE_PORTAL_MIME);
         }
 #endif
         if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_MIME)) {
-            has_mime = SDL_TRUE;
+            data_device->has_mime_file = SDL_TRUE;
             wl_data_offer_accept(id, serial, FILE_MIME);
         }
 
+        if (Wayland_data_offer_has_mime(data_device->drag_offer, TEXT_MIME)) {
+            data_device->has_mime_text = SDL_TRUE;
+            wl_data_offer_accept(id, serial, TEXT_MIME);
+        }
+
         /* SDL only supports "copy" style drag and drop */
-        if (has_mime) {
+        if (data_device->has_mime_file == SDL_TRUE ||
+            data_device->has_mime_text == SDL_TRUE) {
             dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
         } else {
             /* drag_mime is NULL this will decline the offer */
@@ -2041,10 +2067,30 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_
             SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
             if (window) {
                 data_device->dnd_window = window->sdlwindow;
+                const float dx = (float)wl_fixed_to_double(x);
+                const float dy = (float)wl_fixed_to_double(y);
+                SDL_SendDropPosition(data_device->dnd_window, dx, dy);
+                SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                             ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d into window %d for serial %d\n",
+                             WAYLAND_wl_proxy_get_id((struct wl_proxy *) id),
+                             wl_fixed_to_int(x), wl_fixed_to_int(y), SDL_GetWindowID(data_device->dnd_window), serial);
             } else {
                 data_device->dnd_window = NULL;
+                SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                             ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d\n",
+                             WAYLAND_wl_proxy_get_id((struct wl_proxy *) id),
+                             wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
             }
+        } else {
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d\n",
+                         WAYLAND_wl_proxy_get_id((struct wl_proxy *) id),
+                         wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
         }
+    } else {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_enter on data_offer 0x%08x at %d x %d for serial %d\n",
+                     -1, wl_fixed_to_int(x), wl_fixed_to_int(y), serial);
     }
 }
 
@@ -2053,9 +2099,26 @@ static void data_device_handle_leave(void *data, struct wl_data_device *wl_data_
     SDL_WaylandDataDevice *data_device = data;
 
     if (data_device->drag_offer) {
+        if (data_device->dnd_window) {
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x from window %d for serial %d\n",
+                         WAYLAND_wl_proxy_get_id((struct wl_proxy *) data_device->drag_offer->offer),
+                         SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
+        } else {
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x for serial %d\n",
+                         WAYLAND_wl_proxy_get_id((struct wl_proxy *) data_device->drag_offer->offer),
+                         data_device->drag_serial);
+        }
         Wayland_data_offer_destroy(data_device->drag_offer);
         data_device->drag_offer = NULL;
+    } else {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_leave on data_offer 0x%08x for serial %d\n",
+                     -1, -1);
     }
+    data_device->has_mime_file = SDL_FALSE;
+    data_device->has_mime_text = SDL_FALSE;
 }
 
 static void data_device_handle_motion(void *data, struct wl_data_device *wl_data_device,
@@ -2063,7 +2126,9 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_data
 {
     SDL_WaylandDataDevice *data_device = data;
 
-    if (data_device->drag_offer && data_device->dnd_window) {
+    if (data_device->drag_offer && data_device->dnd_window &&
+        (data_device->has_mime_file == SDL_TRUE ||
+         data_device->has_mime_text == SDL_TRUE)) {
         const float dx = (float)wl_fixed_to_double(x);
         const float dy = (float)wl_fixed_to_double(y);
 
@@ -2072,6 +2137,15 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_data
          *      hammer the DBus interface hundreds or even thousands of times per second.
          */
         SDL_SendDropPosition(data_device->dnd_window, dx, dy);
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_motion on data_offer 0x%08x at %d x %d in window %d serial %d\n",
+                     WAYLAND_wl_proxy_get_id((struct wl_proxy *) data_device->drag_offer->offer),
+                     wl_fixed_to_int(x), wl_fixed_to_int(y),
+                     SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
+    } else {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_motion on data_offer 0x%08x at %d x %d serial %d\n",
+                     -1, wl_fixed_to_int(x), wl_fixed_to_int(y), -1);
     }
 }
 
@@ -2079,13 +2153,18 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
 {
     SDL_WaylandDataDevice *data_device = data;
 
-    if (data_device->drag_offer && data_device->dnd_window) {
+    if (data_device->drag_offer && data_device->dnd_window &&
+        (data_device->has_mime_file == SDL_TRUE ||
+         data_device->has_mime_text == SDL_TRUE)) {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_drop on data_offer 0x%08x in window %d serial %d\n",
+                     WAYLAND_wl_proxy_get_id((struct wl_proxy *) data_device->drag_offer->offer),
+                     SDL_GetWindowID(data_device->dnd_window), data_device->drag_serial);
         /* TODO: SDL Support more mime types */
         size_t length;
         SDL_bool drop_handled = SDL_FALSE;
 #ifdef SDL_USE_LIBDBUS
-        if (Wayland_data_offer_has_mime(
-            data_device->drag_offer, FILE_PORTAL_MIME)) {
+        if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_PORTAL_MIME)) {
             void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
                                                       FILE_PORTAL_MIME, &length);
             if (buffer) {
@@ -2112,21 +2191,46 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
          * When running a flatpak sandbox this will most likely be a list of
          * non paths that are not visible to the application
          */
-        if (!drop_handled && Wayland_data_offer_has_mime(
-                                                         data_device->drag_offer, FILE_MIME)) {
+        if (!drop_handled) {
+            const char *mime_type = (data_device->has_mime_file ? FILE_MIME :
+                                    (data_device->has_mime_text ? TEXT_MIME : ""));
             void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
-                                                      FILE_MIME, &length);
-            if (buffer) {
-                char *saveptr = NULL;
-                char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
-                while (token) {
-                    if (SDL_URIToLocal(token, token) >= 0) {
-                        SDL_SendDropFile(data_device->dnd_window, NULL, token);
+                                                      mime_type, &length);
+            if (data_device->has_mime_file) {
+                if (buffer) {
+                    char *saveptr = NULL;
+                    char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
+                    while (token) {
+                        if (SDL_URIToLocal(token, token) >= 0) {
+                            SDL_SendDropFile(data_device->dnd_window, NULL, token);
+                        }
+                        token = SDL_strtok_r(NULL, "\r\n", &saveptr);
                     }
-                    token = SDL_strtok_r(NULL, "\r\n", &saveptr);
+                    SDL_free(buffer);
+                    SDL_SendDropComplete(data_device->dnd_window);
+                } else {
+                    SDL_SendDropComplete(data_device->dnd_window);
+                }
+                drop_handled = SDL_TRUE;
+            } else if (data_device->has_mime_text) {
+                if (buffer) {
+                    char *saveptr = NULL;
+                    char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
+                    while (token) {
+                        SDL_SendDropText(data_device->dnd_window, token);
+                        token = SDL_strtok_r(NULL, "\r\n", &saveptr);
+                    }
+                    SDL_free(buffer);
+                    SDL_SendDropComplete(data_device->dnd_window);
+                } else {
+                    /* Even though there has been a valid data offer,
+                     *  and there have been valid Enter, Motion, and Drop callbacks,
+                     *  Wayland_data_offer_receive may return an empty buffer,
+                     *  because the data is actually in the primary selection device,
+                     *  not in the data device.
+                     */
+                    SDL_SendDropComplete(data_device->dnd_window);
                 }
-                SDL_SendDropComplete(data_device->dnd_window);
-                SDL_free(buffer);
                 drop_handled = SDL_TRUE;
             }
         }
@@ -2135,6 +2239,10 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
             WL_DATA_OFFER_FINISH_SINCE_VERSION) {
             wl_data_offer_finish(data_device->drag_offer->offer);
         }
+    } else {
+        SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                     ". In wl_data_device_listener . data_device_handle_drop on data_offer 0x%08x serial %d\n",
+                     -1, -1);
     }
 
     Wayland_data_offer_destroy(data_device->drag_offer);
@@ -2151,6 +2259,9 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d
         offer = wl_data_offer_get_user_data(id);
     }
 
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In data_device_listener . data_device_handle_selection on data_offer 0x%08x\n",
+                 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) id) : -1));
     if (data_device->selection_offer != offer) {
         Wayland_data_offer_destroy(data_device->selection_offer);
         data_device->selection_offer = offer;
@@ -2179,6 +2290,9 @@ static void primary_selection_device_handle_offer(void *data, struct zwp_primary
         zwp_primary_selection_offer_v1_set_user_data(id, primary_selection_offer);
         zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, primary_selection_offer);
     }
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_offer on primary_selection_offer 0x%08x\n",
+                 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) id) : -1));
 }
 
 static void primary_selection_device_handle_selection(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
@@ -2195,6 +2309,9 @@ static void primary_selection_device_handle_selection(void *data, struct zwp_pri
         Wayland_primary_selection_offer_destroy(primary_selection_device->selection_offer);
         primary_selection_device->selection_offer = offer;
     }
+    SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                 ". In zwp_primary_selection_device_v1_listener . primary_selection_device_handle_selection on primary_selection_offer 0x%08x\n",
+                 (id ? WAYLAND_wl_proxy_get_id((struct wl_proxy *) id) : -1));
 
     SDL_SendClipboardUpdate();
 }