Browse Source

Fixed 2942 - Wayland: Drag and Drop / Clipboard

x414e54

I have implemented Drag and Drop and Clipboard support for Wayland.

Drag and dropping files from nautilus to the testdropfile application seems to work and also copy and paste.
Sam Lantinga 8 years ago
parent
commit
d767a450dc

+ 123 - 0
src/video/wayland/SDL_waylandclipboard.c

@@ -0,0 +1,123 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2016 Sam Lantinga <[email protected]>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#if SDL_VIDEO_DRIVER_WAYLAND
+
+#include "SDL_waylanddatamanager.h"
+#include "SDL_waylandevents_c.h"
+
+int
+Wayland_SetClipboardText(_THIS, const char *text)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandDataDevice *data_device = NULL;
+    
+    int status = 0;
+ 
+    if (_this == NULL || _this->driverdata == NULL) {
+        status = SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        /* TODO: Support more than one seat */ 
+        data_device = Wayland_get_data_device(video_data->input);
+        if (text[0] != '\0') {
+            SDL_WaylandDataSource* source = Wayland_data_source_create(_this);
+            Wayland_data_source_add_data(source, TEXT_MIME, text,
+                                         strlen(text) + 1); 
+
+            status = Wayland_data_device_set_selection(data_device, source);
+            if (status != 0) {
+                Wayland_data_source_destroy(source);
+            }
+        } else {
+            status = Wayland_data_device_clear_selection(data_device);
+        }
+    }
+
+    return status;
+}
+
+char *
+Wayland_GetClipboardText(_THIS)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandDataDevice *data_device = NULL;
+
+    char *text = NULL;
+
+    void *buffer = NULL;
+    size_t length = 0;
+ 
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        /* TODO: Support more than one seat */ 
+        data_device = Wayland_get_data_device(video_data->input);
+        if (data_device->selection_offer != NULL) {
+            buffer = Wayland_data_offer_receive(data_device->selection_offer,
+                                                &length, TEXT_MIME, SDL_TRUE);
+            if (length > 0) {
+                text = (char*) buffer;
+            } 
+        } else if (data_device->selection_source != NULL) {
+            buffer = Wayland_data_source_get_data(data_device->selection_source,
+                                                  &length, TEXT_MIME, SDL_TRUE);
+            if (length > 0) {
+                text = (char*) buffer;
+            } 
+        }
+    }
+
+    if (text == NULL) {
+        text = SDL_strdup("");
+    }
+
+    return text;
+}
+
+SDL_bool
+Wayland_HasClipboardText(_THIS)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandDataDevice *data_device = NULL;
+
+    SDL_bool result = SDL_FALSE;    
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        data_device = Wayland_get_data_device(video_data->input);
+        if (data_device != NULL && Wayland_data_offer_has_mime(
+                data_device->selection_offer, TEXT_MIME)) {
+            result = SDL_TRUE;
+        } else if(data_device != NULL && Wayland_data_source_has_mime(
+                data_device->selection_source, TEXT_MIME)) {
+            result = SDL_TRUE;
+        }
+    }
+    return result;
+}
+
+#endif /* SDL_VIDEO_DRIVER_WAYLAND */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 32 - 0
src/video/wayland/SDL_waylandclipboard.h

@@ -0,0 +1,32 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2016 Sam Lantinga <[email protected]>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifndef _SDL_waylandclipboard_h
+#define _SDL_waylandclipboard_h
+
+extern int Wayland_SetClipboardText(_THIS, const char *text);
+extern char *Wayland_GetClipboardText(_THIS);
+extern SDL_bool Wayland_HasClipboardText(_THIS);
+
+#endif /* _SDL_waylandclipboard_h */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 484 - 0
src/video/wayland/SDL_waylanddatamanager.c

@@ -0,0 +1,484 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2016 Sam Lantinga <[email protected]>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "../../SDL_internal.h"
+
+#if SDL_VIDEO_DRIVER_WAYLAND
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "SDL_stdinc.h"
+#include "SDL_assert.h"
+
+#include "SDL_waylandvideo.h"
+#include "SDL_waylanddatamanager.h"
+
+#include "SDL_waylanddyn.h"
+
+static ssize_t
+write_pipe(int fd, const void* buffer, size_t total_length, size_t *pos)
+{
+    int ready = 0;
+    ssize_t bytes_written = 0;
+    ssize_t length = total_length - *pos;
+
+    sigset_t sig_set;
+    sigset_t old_sig_set;
+    struct timespec zerotime = {0};
+    fd_set set;
+    struct timeval timeout;
+
+    FD_ZERO(&set);
+    FD_SET(fd, &set);
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    ready = select(fd + 1, NULL, &set, NULL, &timeout);
+
+    sigemptyset(&sig_set);
+    sigaddset(&sig_set, SIGPIPE);  
+
+    pthread_sigmask(SIG_BLOCK, &sig_set, &old_sig_set); 
+
+    if (ready == 0) {
+        bytes_written = SDL_SetError("Pipe timeout");
+    } else if (ready < 0) {
+        bytes_written = SDL_SetError("Pipe select error");
+    } else {
+        if (length > 0) {
+            bytes_written = write(fd, buffer + *pos, SDL_min(length, PIPE_BUF));
+        }
+
+        if (bytes_written > 0) {
+            *pos += bytes_written;
+        }
+    }
+
+    sigtimedwait(&sig_set, 0, &zerotime);
+    pthread_sigmask(SIG_SETMASK, &old_sig_set, NULL);
+
+    return bytes_written;
+}
+
+static ssize_t
+read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate)
+{
+    int ready = 0;
+    void* output_buffer = NULL;
+    char temp[PIPE_BUF];
+    size_t new_buffer_length = 0;
+    ssize_t bytes_read = 0;
+    size_t pos = 0;
+
+    fd_set set;
+    struct timeval timeout;
+
+    FD_ZERO(&set);
+    FD_SET(fd, &set);
+
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+
+    ready = select(fd + 1, &set, NULL, NULL, &timeout);  
+  
+    if (ready == 0) {
+        bytes_read = SDL_SetError("Pipe timeout");
+    } else if (ready < 0) {
+        bytes_read = SDL_SetError("Pipe select error");
+    } else {
+        bytes_read = read(fd, temp, sizeof(temp));
+    }
+
+    if (bytes_read > 0) {
+        pos = *total_length;
+        *total_length += bytes_read;
+
+        if (null_terminate == SDL_TRUE) {
+            new_buffer_length = *total_length + 1;
+        } else {
+            new_buffer_length = *total_length;
+        }
+
+        if (*buffer == NULL) {
+            output_buffer = SDL_malloc(new_buffer_length);
+        } else {
+            output_buffer = SDL_realloc(*buffer, new_buffer_length);
+        }           
+        
+        if (output_buffer == NULL) {
+            bytes_read = SDL_OutOfMemory();
+        } else {
+            SDL_memcpy(output_buffer + pos, temp, bytes_read);
+
+            if (null_terminate == SDL_TRUE) {
+                SDL_memset(output_buffer + (new_buffer_length - 1), 0, 1);
+            }
+            
+            *buffer = output_buffer;
+        }
+    }
+
+    return bytes_read;
+}
+
+#define MIME_LIST_SIZE 4
+
+static const char* mime_conversion_list[MIME_LIST_SIZE][2] = {
+    {"text/plain", TEXT_MIME},
+    {"TEXT", TEXT_MIME},
+    {"UTF8_STRING", TEXT_MIME},
+    {"STRING", TEXT_MIME}
+};
+
+const char*
+Wayland_convert_mime_type(const char *mime_type)
+{
+    const char *found = mime_type;
+
+    size_t index = 0;
+
+    for (index = 0; index < MIME_LIST_SIZE; ++index) {
+        if (strcmp(mime_conversion_list[index][0], mime_type) == 0) {
+            found = mime_conversion_list[index][1];
+            break;
+        }
+    }
+    
+    return found;
+}
+
+static SDL_MimeDataList*
+mime_data_list_find(struct wl_list* list, 
+                    const char* mime_type)
+{
+    SDL_MimeDataList *found = NULL;
+
+    SDL_MimeDataList *mime_list = NULL;
+    wl_list_for_each(mime_list, list, link) { 
+        if (strcmp(mime_list->mime_type, mime_type) == 0) {
+            found = mime_list;
+            break;
+        }
+    }    
+    return found;
+}
+
+static int
+mime_data_list_add(struct wl_list* list, 
+                   const char* mime_type,
+                   void* buffer, size_t length)
+{
+    int status = 0;
+    size_t mime_type_length = 0;
+
+    SDL_MimeDataList *mime_data = NULL;
+
+    mime_data = mime_data_list_find(list, mime_type);
+
+    if (mime_data == NULL) {
+        mime_data = SDL_calloc(1, sizeof(*mime_data));
+        if (mime_data == NULL) {
+            status = SDL_OutOfMemory();
+        } else {
+            WAYLAND_wl_list_insert(list, &(mime_data->link));
+
+            mime_type_length = strlen(mime_type) + 1;
+            mime_data->mime_type = SDL_malloc(mime_type_length);
+            if (mime_data->mime_type == NULL) {
+                status = SDL_OutOfMemory();
+            } else {
+                SDL_memcpy(mime_data->mime_type, mime_type, mime_type_length);
+            }
+        }
+    }
+    
+    if (mime_data != NULL && buffer != NULL && length > 0) {
+        if (mime_data->data != NULL) {
+            SDL_free(mime_data->data);
+        }
+        mime_data->data = buffer;
+        mime_data->length = length;
+    }
+
+    return status;
+}
+
+static void
+mime_data_list_free(struct wl_list *list)
+{
+    SDL_MimeDataList *mime_data = NULL; 
+    SDL_MimeDataList *next = NULL;
+
+    wl_list_for_each_safe(mime_data, next, list, link) {
+        if (mime_data->data != NULL) {
+            SDL_free(mime_data->data);
+        }        
+        if (mime_data->mime_type != NULL) {
+            SDL_free(mime_data->mime_type);
+        }
+        SDL_free(mime_data);       
+    } 
+}
+
+ssize_t 
+Wayland_data_source_send(SDL_WaylandDataSource *source,  
+                         const char *mime_type, int fd)
+{
+    size_t written_bytes = 0;
+    ssize_t status = 0;
+    SDL_MimeDataList *mime_data = NULL;
+ 
+    mime_type = Wayland_convert_mime_type(mime_type);
+    mime_data = mime_data_list_find(&source->mimes,
+                                                      mime_type);
+
+    if (mime_data == NULL || mime_data->data == NULL) {
+        status = SDL_SetError("Invalid mime type");
+        close(fd);
+    } else {
+        while (write_pipe(fd, mime_data->data, mime_data->length,
+                          &written_bytes) > 0);
+        close(fd);
+        status = written_bytes;
+    }
+    return status;
+}
+
+int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
+                                 const char *mime_type,
+                                 const void *buffer,
+                                 size_t length) 
+{
+    int status = 0;
+    if (length > 0) {
+        void *internal_buffer = SDL_malloc(length);
+        if (internal_buffer == NULL) {
+            status = SDL_OutOfMemory();
+        } else {
+            SDL_memcpy(internal_buffer, buffer, length);
+            status = mime_data_list_add(&source->mimes, mime_type, 
+                                        internal_buffer, length);
+        }
+    }
+    return status;
+}
+
+SDL_bool 
+Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
+                             const char *mime_type)
+{
+    SDL_bool found = SDL_FALSE;
+
+    if (source != NULL) {
+        found = mime_data_list_find(&source->mimes, mime_type) != NULL;
+    }
+    return found;
+}
+
+void* 
+Wayland_data_source_get_data(SDL_WaylandDataSource *source,
+                             size_t *length, const char* mime_type,
+                             SDL_bool null_terminate)
+{
+    SDL_MimeDataList *mime_data = NULL;
+    void *buffer = NULL;
+    *length = 0;
+
+    if (source == NULL) {
+        SDL_SetError("Invalid data source");
+    } else {
+        mime_data = mime_data_list_find(&source->mimes, mime_type);
+        if (mime_data != NULL && mime_data->length > 0) {
+            buffer = SDL_malloc(mime_data->length);
+            if (buffer == NULL) {
+                *length = SDL_OutOfMemory();
+            } else {
+                *length = mime_data->length;
+                SDL_memcpy(buffer, mime_data->data, mime_data->length);
+            }
+       }
+    }
+
+    return buffer;
+}
+
+void
+Wayland_data_source_destroy(SDL_WaylandDataSource *source)
+{
+    if (source != NULL) {
+        wl_data_source_destroy(source->source);
+        mime_data_list_free(&source->mimes);
+        SDL_free(source);
+    }
+}
+
+void* 
+Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
+                           size_t *length, const char* mime_type,
+                           SDL_bool null_terminate)
+{
+    SDL_WaylandDataDevice *data_device = NULL;
+ 
+    int pipefd[2];
+    void *buffer = NULL;
+    *length = 0;
+
+    if (offer == NULL) {
+        SDL_SetError("Invalid data offer");
+    } else if (pipe2(pipefd, O_CLOEXEC|O_NONBLOCK) == -1) {
+        SDL_SetError("Could not read pipe");
+    } else if ((data_device = offer->data_device) == NULL) {
+        SDL_SetError("Data device not initialized");
+    } else {
+        wl_data_offer_receive(offer->offer, mime_type, pipefd[1]);
+
+        /* TODO: Needs pump and flush? */
+        WAYLAND_wl_display_flush(data_device->video_data->display);
+
+        close(pipefd[1]);
+        
+        while (read_pipe(pipefd[0], &buffer, length, null_terminate) > 0);
+        close(pipefd[0]);
+    }
+    return buffer;
+}
+
+int 
+Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
+                            const char* mime_type)
+{
+    return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
+}
+
+
+SDL_bool 
+Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
+                            const char *mime_type)
+{
+    SDL_bool found = SDL_FALSE;
+
+    if (offer != NULL) {
+        found = mime_data_list_find(&offer->mimes, mime_type) != NULL;
+    }
+    return found;
+}
+
+void
+Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer)
+{
+    if (offer != NULL) {
+        wl_data_offer_destroy(offer->offer);
+        mime_data_list_free(&offer->mimes);
+        SDL_free(offer);
+    }
+}
+
+int
+Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
+{
+    int status = 0;
+
+    if (data_device == NULL || data_device->data_device == NULL) {
+        status = SDL_SetError("Invalid Data Device");
+    } else if (data_device->selection_source != 0) {
+        wl_data_device_set_selection(data_device->data_device, NULL, 0);
+        data_device->selection_source = NULL;
+    }
+    return status;
+}
+
+int
+Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
+                                  SDL_WaylandDataSource *source)
+{
+    int status = 0;
+    size_t num_offers = 0;
+    size_t index = 0;
+
+    if (data_device == NULL) {
+        status = SDL_SetError("Invalid Data Device");
+    } else if (source == NULL) {
+        status = SDL_SetError("Invalid source");
+    } else {
+        SDL_MimeDataList *mime_data = NULL;
+
+        wl_list_for_each(mime_data, &(source->mimes), link) {
+            wl_data_source_offer(source->source,
+                                 mime_data->mime_type); 
+
+            /* TODO - Improve system for multiple mime types to same data */
+            for (index = 0; index < MIME_LIST_SIZE; ++index) {
+                if (strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) {
+                    wl_data_source_offer(source->source,
+                                         mime_conversion_list[index][0]);
+               }
+            }
+            /* */
+ 
+            ++num_offers;
+        } 
+
+        if (num_offers == 0) {
+            Wayland_data_device_clear_selection(data_device);
+            status = SDL_SetError("No mime data");
+        } else {
+            /* 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); 
+            }
+            data_device->selection_source = source;
+        }
+    }
+
+    return status;
+}
+
+int
+Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device,
+                               uint32_t serial)
+{
+    int status = -1;
+    if (data_device != NULL) {
+        status = 0;
+
+        /* If there was no serial and there is a pending selection set it now. */
+        if (data_device->selection_serial == 0
+            && data_device->selection_source != NULL) {
+            wl_data_device_set_selection(data_device->data_device,
+                                         data_device->selection_source->source,
+                                         serial); 
+        }
+
+        data_device->selection_serial = serial;
+    }
+
+    return status; 
+}
+
+#endif /* SDL_VIDEO_DRIVER_WAYLAND */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 105 - 0
src/video/wayland/SDL_waylanddatamanager.h

@@ -0,0 +1,105 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2016 Sam Lantinga <[email protected]>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "../../SDL_internal.h"
+
+#ifndef _SDL_waylanddatamanager_h
+#define _SDL_waylanddatamanager_h
+
+#include "SDL_waylandvideo.h"
+#include "SDL_waylandwindow.h"
+
+#define TEXT_MIME "text/plain;charset=utf-8"
+#define FILE_MIME "text/uri-list"
+
+typedef struct {
+    char *mime_type;
+    void *data;
+    size_t length;
+    struct wl_list link;
+} SDL_MimeDataList;
+
+typedef struct {
+    struct wl_data_source *source;
+    struct wl_list mimes;
+} SDL_WaylandDataSource;
+
+typedef struct {
+    struct wl_data_offer *offer;
+    struct wl_list mimes;
+    void *data_device;
+} SDL_WaylandDataOffer;
+
+typedef struct {
+    struct wl_data_device *data_device;
+    SDL_VideoData *video_data;
+
+    /* Drag and Drop */
+    uint32_t drag_serial;
+    SDL_WaylandDataOffer *drag_offer;
+    SDL_WaylandDataOffer *selection_offer;
+
+    /* Clipboard */
+    uint32_t selection_serial;
+    SDL_WaylandDataSource *selection_source;
+} SDL_WaylandDataDevice;
+
+extern const char* Wayland_convert_mime_type(const char *mime_type);
+
+/* Wayland Data Source - (Sending) */
+extern SDL_WaylandDataSource* Wayland_data_source_create(_THIS);
+extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, 
+                                        const char *mime_type, int fd);
+extern int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
+                                        const char *mime_type, 
+                                        const void *buffer, 
+                                        size_t length);
+extern SDL_bool Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
+                                             const char *mime_type);
+extern void* Wayland_data_source_get_data(SDL_WaylandDataSource *source,
+                                          size_t *length,
+                                          const char *mime_type,
+                                          SDL_bool null_terminate);
+extern void Wayland_data_source_destroy(SDL_WaylandDataSource *source);
+
+/* Wayland Data Offer - (Receiving) */
+extern void* Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
+                                        size_t *length,
+                                        const char *mime_type,
+                                        SDL_bool null_terminate);
+extern SDL_bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
+                                            const char *mime_type);
+extern int Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
+                                       const char *mime_type);
+extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer);
+
+/* Clipboard */
+extern int Wayland_data_device_clear_selection(SDL_WaylandDataDevice *device);
+extern int Wayland_data_device_set_selection(SDL_WaylandDataDevice *device,
+                                             SDL_WaylandDataSource *source);
+extern int Wayland_data_device_set_serial(SDL_WaylandDataDevice *device,
+                                          uint32_t serial);
+#endif /* _SDL_waylanddatamanager_h */
+
+/* vi: set ts=4 sw=4 expandtab: */
+
+
+

+ 4 - 0
src/video/wayland/SDL_waylanddyn.h

@@ -91,6 +91,10 @@ void SDL_WAYLAND_UnloadSymbols(void);
 #define wl_output_interface (*WAYLAND_wl_output_interface)
 #define wl_shell_interface (*WAYLAND_wl_shell_interface)
 #define wl_shm_interface (*WAYLAND_wl_shm_interface)
+#define wl_data_device_interface (*WAYLAND_wl_data_device_interface)
+#define wl_data_offer_interface (*WAYLAND_wl_data_offer_interface)
+#define wl_data_source_interface (*WAYLAND_wl_data_source_interface)
+#define wl_data_device_manager_interface (*WAYLAND_wl_data_device_manager_interface)
 
 #endif /* SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC */
 

+ 285 - 0
src/video/wayland/SDL_waylandevents.c

@@ -52,6 +52,7 @@ struct SDL_WaylandInput {
     struct wl_seat *seat;
     struct wl_pointer *pointer;
     struct wl_keyboard *keyboard;
+    SDL_WaylandDataDevice *data_device;
     struct zwp_relative_pointer_v1 *relative_pointer;
     SDL_WindowData *pointer_focus;
     SDL_WindowData *keyboard_focus;
@@ -208,6 +209,8 @@ pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_t serial,
             default:
                 return;
         }
+            
+        Wayland_data_device_set_serial(input->data_device, serial); 
 
         SDL_SendMouseButton(window->sdlwindow, 0,
                             state ? SDL_PRESSED : SDL_RELEASED, sdl_button);
@@ -373,6 +376,9 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
 
         if (size > 0) {
             text[size] = 0;
+
+            Wayland_data_device_set_serial(input->data_device, serial);
+
             SDL_SendKeyboardText(text);
         }
     }
@@ -430,10 +436,245 @@ static const struct wl_seat_listener seat_listener = {
     seat_handle_capabilities,
 };
 
+static void
+data_source_handle_target(void *data, struct wl_data_source *wl_data_source,
+                          const char *mime_type)
+{
+}
+
+static void
+data_source_handle_send(void *data, struct wl_data_source *wl_data_source,
+                        const char *mime_type, int32_t fd)
+{
+    Wayland_data_source_send((SDL_WaylandDataSource *)data, mime_type, fd);
+}
+                       
+static void
+data_source_handle_cancelled(void *data, struct wl_data_source *wl_data_source)
+{
+    Wayland_data_source_destroy(data);
+}
+                       
+static void
+data_source_handle_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
+{
+}
+
+static void
+data_source_handle_dnd_finished(void *data, struct wl_data_source *wl_data_source)
+{
+}
+
+static void
+data_source_handle_action(void *data, struct wl_data_source *wl_data_source,
+                          uint32_t dnd_action)
+{
+}
+
+static const struct wl_data_source_listener data_source_listener = {
+    data_source_handle_target,
+    data_source_handle_send,
+    data_source_handle_cancelled,
+    data_source_handle_dnd_drop_performed, // Version 3
+    data_source_handle_dnd_finished,       // Version 3
+    data_source_handle_action,             // Version 3
+};
+
+SDL_WaylandDataSource*
+Wayland_data_source_create(_THIS)
+{
+    SDL_WaylandDataSource *data_source = NULL;
+    SDL_VideoData *driver_data = NULL;
+    struct wl_data_source *id = NULL;
+
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        driver_data = _this->driverdata;
+
+        if (driver_data->data_device_manager != NULL) {
+            id = wl_data_device_manager_create_data_source(
+                     driver_data->data_device_manager);
+        }
+
+        if (id == NULL) { 
+            SDL_SetError("Wayland unable to create data source");
+        } else {
+            data_source = SDL_calloc(1, sizeof *data_source);
+            if (data_source == NULL) {
+                SDL_OutOfMemory();
+                wl_data_source_destroy(id);
+            } else {
+                WAYLAND_wl_list_init(&(data_source->mimes));
+                data_source->source = id;
+                wl_data_source_set_user_data(id, data_source);
+                wl_data_source_add_listener(id, &data_source_listener,
+                                            data_source);
+            }
+        }
+    }
+    return data_source;
+}
+
+static void
+data_offer_handle_offer(void *data, struct wl_data_offer *wl_data_offer,
+                        const char *mime_type)
+{
+    SDL_WaylandDataOffer *offer = data;
+    Wayland_data_offer_add_mime(offer, mime_type);
+}
+
+static void
+data_offer_handle_source_actions(void *data, struct wl_data_offer *wl_data_offer,
+                                 uint32_t source_actions)
+{
+}
+
+static void
+data_offer_handle_actions(void *data, struct wl_data_offer *wl_data_offer,
+                          uint32_t dnd_action)
+{
+}
+
+static const struct wl_data_offer_listener data_offer_listener = {
+    data_offer_handle_offer,
+    data_offer_handle_source_actions, // Version 3
+    data_offer_handle_actions,        // Version 3
+};
+
+static void
+data_device_handle_data_offer(void *data, struct wl_data_device *wl_data_device,
+			                  struct wl_data_offer *id)
+{
+    SDL_WaylandDataOffer *data_offer = NULL;
+
+    data_offer = SDL_calloc(1, sizeof *data_offer);
+    if (data_offer == NULL) {
+        SDL_OutOfMemory();
+    } else {
+        data_offer->offer = id;
+        data_offer->data_device = data;
+        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);
+    }
+}
+
+static void
+data_device_handle_enter(void *data, struct wl_data_device *wl_data_device,
+		                 uint32_t serial, struct wl_surface *surface,
+                         wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id)
+{
+    SDL_WaylandDataDevice *data_device = data;
+    SDL_bool has_mime = SDL_FALSE;
+    uint32_t dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; 
+        
+    data_device->drag_serial = serial;
+
+    if (id != NULL) {
+        data_device->drag_offer = wl_data_offer_get_user_data(id);
+
+        /* TODO: SDL Support more mime types */
+        has_mime = Wayland_data_offer_has_mime(
+            data_device->drag_offer, FILE_MIME);
+
+        /* If drag_mime is NULL this will decline the offer */
+        wl_data_offer_accept(id, serial,
+                             (has_mime == SDL_TRUE) ? FILE_MIME : NULL);
+
+        /* SDL only supports "copy" style drag and drop */
+        if (has_mime == SDL_TRUE) {
+            dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+        }
+        wl_data_offer_set_actions(data_device->drag_offer->offer,
+                                  dnd_action, dnd_action);
+    }
+}
+
+static void
+data_device_handle_leave(void *data, struct wl_data_device *wl_data_device)
+{
+    SDL_WaylandDataDevice *data_device = data;
+    SDL_WaylandDataOffer *offer = NULL;
+
+    if (data_device->selection_offer != NULL) {
+        data_device->selection_offer = NULL;
+        Wayland_data_offer_destroy(offer);
+    }
+}
+
+static void
+data_device_handle_motion(void *data, struct wl_data_device *wl_data_device,
+		                  uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+}
+
+static void
+data_device_handle_drop(void *data, struct wl_data_device *wl_data_device)
+{
+    SDL_WaylandDataDevice *data_device = data;
+    void *buffer = NULL;
+    size_t length = 0;
+
+    const char *current_uri = NULL;
+    const char *last_char = NULL;
+    char *current_char = NULL;
+    
+    if (data_device->drag_offer != NULL) {
+        /* TODO: SDL Support more mime types */
+        buffer = Wayland_data_offer_receive(data_device->drag_offer,
+                                            &length, FILE_MIME, SDL_FALSE);
+
+        /* uri-list */
+        current_uri = (const char *)buffer;
+        last_char = (const char *)buffer + length;
+        for (current_char = buffer; current_char < last_char; ++current_char) {
+            if (*current_char == '\n' || *current_char == 0) {
+                if (*current_uri != 0 && *current_uri != '#') {
+                    *current_char = 0;
+                    SDL_SendDropFile(NULL, current_uri);
+                }
+                current_uri = (const char *)current_char + 1;
+            }
+        }
+
+        SDL_free(buffer);
+    }
+}
+
+static void
+data_device_handle_selection(void *data, struct wl_data_device *wl_data_device,
+			                 struct wl_data_offer *id)
+{    
+    SDL_WaylandDataDevice *data_device = data;
+    SDL_WaylandDataOffer *offer = NULL;
+
+    if (id != NULL) {
+        offer = wl_data_offer_get_user_data(id);
+    }
+
+    if (data_device->selection_offer != offer) {
+        Wayland_data_offer_destroy(data_device->selection_offer);
+        data_device->selection_offer = offer;
+    }
+
+    SDL_SendClipboardUpdate();
+}
+
+static const struct wl_data_device_listener data_device_listener = {
+    data_device_handle_data_offer,
+    data_device_handle_enter,
+    data_device_handle_leave,
+    data_device_handle_motion,
+    data_device_handle_drop,
+    data_device_handle_selection
+};
+
 void
 Wayland_display_add_input(SDL_VideoData *d, uint32_t id)
 {
     struct SDL_WaylandInput *input;
+    SDL_WaylandDataDevice *data_device = NULL;
 
     input = SDL_calloc(1, sizeof *input);
     if (input == NULL)
@@ -444,6 +685,27 @@ Wayland_display_add_input(SDL_VideoData *d, uint32_t id)
     input->sx_w = wl_fixed_from_int(0);
     input->sy_w = wl_fixed_from_int(0);
     d->input = input;
+    
+    if (d->data_device_manager != NULL) {
+        data_device = SDL_calloc(1, sizeof *data_device);
+        if (data_device == NULL) {
+            return;
+        }
+
+        data_device->data_device = wl_data_device_manager_get_data_device(
+            d->data_device_manager, input->seat
+        );
+        data_device->video_data = d;
+
+        if (data_device->data_device == NULL) {
+            SDL_free(data_device);
+        } else {
+            wl_data_device_set_user_data(data_device->data_device, data_device);
+            wl_data_device_add_listener(data_device->data_device,
+                                        &data_device_listener, data_device);
+            input->data_device = data_device;
+        }
+    }
 
     wl_seat_add_listener(input->seat, &seat_listener, input);
     wl_seat_set_user_data(input->seat, input);
@@ -458,6 +720,20 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
     if (!input)
         return;
 
+    if (input->data_device != NULL) {
+        Wayland_data_device_clear_selection(input->data_device);
+        if (input->data_device->selection_offer != NULL) {
+            Wayland_data_offer_destroy(input->data_device->selection_offer);
+        }
+        if (input->data_device->drag_offer != NULL) {
+            Wayland_data_offer_destroy(input->data_device->drag_offer);
+        }
+        if (input->data_device->data_device != NULL) {
+            wl_data_device_release(input->data_device->data_device);
+        }
+        SDL_free(input->data_device);
+    }
+
     if (input->keyboard)
         wl_keyboard_destroy(input->keyboard);
 
@@ -477,6 +753,15 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
     d->input = NULL;
 }
 
+SDL_WaylandDataDevice* Wayland_get_data_device(struct SDL_WaylandInput *input)
+{
+    if (input == NULL) {
+        return NULL;
+    }
+
+    return input->data_device;
+}
+
 void Wayland_display_add_relative_pointer_manager(SDL_VideoData *d, uint32_t id)
 {
     d->relative_pointer_manager =

+ 5 - 0
src/video/wayland/SDL_waylandevents_c.h

@@ -26,12 +26,17 @@
 
 #include "SDL_waylandvideo.h"
 #include "SDL_waylandwindow.h"
+#include "SDL_waylanddatamanager.h"
+
+struct SDL_WaylandInput;
 
 extern void Wayland_PumpEvents(_THIS);
 
 extern void Wayland_display_add_input(SDL_VideoData *d, uint32_t id);
 extern void Wayland_display_destroy_input(SDL_VideoData *d);
 
+extern SDL_WaylandDataDevice* Wayland_get_data_device(struct SDL_WaylandInput *input);
+
 extern void Wayland_display_add_pointer_constraints(SDL_VideoData *d, uint32_t id);
 extern void Wayland_display_destroy_pointer_constraints(SDL_VideoData *d);
 

+ 4 - 0
src/video/wayland/SDL_waylandsym.h

@@ -83,6 +83,10 @@ SDL_WAYLAND_INTERFACE(wl_compositor_interface)
 SDL_WAYLAND_INTERFACE(wl_output_interface)
 SDL_WAYLAND_INTERFACE(wl_shell_interface)
 SDL_WAYLAND_INTERFACE(wl_shm_interface)
+SDL_WAYLAND_INTERFACE(wl_data_device_interface)
+SDL_WAYLAND_INTERFACE(wl_data_source_interface)
+SDL_WAYLAND_INTERFACE(wl_data_offer_interface)
+SDL_WAYLAND_INTERFACE(wl_data_device_manager_interface)
 
 SDL_WAYLAND_MODULE(WAYLAND_EGL)
 SDL_WAYLAND_SYM(struct wl_egl_window *, wl_egl_window_create, (struct wl_surface *, int, int))

+ 7 - 0
src/video/wayland/SDL_waylandvideo.c

@@ -34,6 +34,7 @@
 #include "SDL_waylandopengles.h"
 #include "SDL_waylandmouse.h"
 #include "SDL_waylandtouch.h"
+#include "SDL_waylandclipboard.h"
 
 #include <sys/types.h>
 #include <unistd.h>
@@ -176,6 +177,10 @@ Wayland_CreateDevice(int devindex)
     device->DestroyWindow = Wayland_DestroyWindow;
     device->SetWindowHitTest = Wayland_SetWindowHitTest;
 
+    device->SetClipboardText = Wayland_SetClipboardText;
+    device->GetClipboardText = Wayland_GetClipboardText;
+    device->HasClipboardText = Wayland_HasClipboardText;
+
     device->free = Wayland_DeleteDevice;
 
     return device;
@@ -312,6 +317,8 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
         Wayland_display_add_relative_pointer_manager(d, id);
     } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) {
         Wayland_display_add_pointer_constraints(d, id);
+    } else if (strcmp(interface, "wl_data_device_manager") == 0) {
+        d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, 3);
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
     } else if (strcmp(interface, "qt_touch_extension") == 0) {
         Wayland_touch_create(d, id);

+ 1 - 0
src/video/wayland/SDL_waylandvideo.h

@@ -46,6 +46,7 @@ typedef struct {
     struct wl_shell *shell;
     struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
     struct zwp_pointer_constraints_v1 *pointer_constraints;
+    struct wl_data_device_manager *data_device_manager;
 
     EGLDisplay edpy;
     EGLContext context;