Преглед изворни кода

Add support for X11 primary selection (#6132)

X11 has a so-called primary selection, which you can use by marking text and middle-clicking elsewhere to copy the marked text.

There are 3 new API functions in `SDL_clipboard.h`, which work exactly like their clipboard equivalents.

## Test Instructions

* Run the tests (just a copy of the clipboard tests): `$ ./test/testautomation --filter Clipboard`
* Build and run this small application:
<details>
```C
#include <SDL.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void print_error(const char *where)
{
	const char *errstr = SDL_GetError();
	if (errstr == NULL || errstr[0] == '\0')
		return;
	fprintf(stderr, "SDL Error after '%s': %s\n", where, errstr);
	SDL_ClearError();
}

int main()
{
	char text_buf[256];

	srand(time(NULL));

	SDL_Init(SDL_INIT_VIDEO);
	print_error("SDL_INIT()");
	SDL_Window *window = SDL_CreateWindow("Primary Selection Test", SDL_WINDOWPOS_UNDEFINED,
			SDL_WINDOWPOS_UNDEFINED, 400, 400, SDL_WINDOW_SHOWN);
	print_error("SDL_CreateWindow()");
	SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
	print_error("SDL_CreateRenderer()");

	bool quit = false;
	unsigned int do_render = 0;
	while (!quit) {
		SDL_Event event;
		while (SDL_PollEvent(&event)) {
			print_error("SDL_PollEvent()");
			switch (event.type) {
			case SDL_QUIT: {
				quit = true;
				break;
			} case SDL_KEYDOWN: {
				switch (event.key.keysym.sym) {
				case SDLK_ESCAPE:
				case SDLK_q:
					quit = true;
					break;
				case SDLK_c:
					snprintf(text_buf, sizeof(text_buf), "foo%d", rand());
					SDL_SetClipboardText(text_buf);
					print_error("SDL_SetClipboardText()");
					printf("clipboard: set_to=\"%s\"\n", text_buf);
					break;
				case SDLK_v: {
					printf("clipboard: has=%d, ", SDL_HasClipboardText());
					print_error("SDL_HasClipboardText()");
					char *text = SDL_GetClipboardText();
					print_error("SDL_GetClipboardText()");
					printf("text=\"%s\"\n", text);
					SDL_free(text);
					break;
				} case SDLK_d:
					snprintf(text_buf, sizeof(text_buf), "bar%d", rand());
					SDL_SetPrimarySelectionText(text_buf);
					print_error("SDL_SetPrimarySelectionText()");
					printf("primselec: set_to=\"%s\"\n", text_buf);
					break;
				case SDLK_f: {
					printf("primselec: has=%d, ", SDL_HasPrimarySelectionText());
					print_error("SDL_HasPrimarySelectionText()");
					char *text = SDL_GetPrimarySelectionText();
					print_error("SDL_GetPrimarySelectionText()");
					printf("text=\"%s\"\n", text);
					SDL_free(text);
					break;
				} default:
					break;
				}
				break;
			} default: {
				break;
			}}
		}
		// create less noise with WAYLAND_DEBUG=1
		if (do_render == 0) {
			SDL_RenderPresent(renderer);
			print_error("SDL_RenderPresent()");
		}
		do_render += 1;
		usleep(12000);
	}

	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
	print_error("quit");
	return 0;
}
```
</details>

* Use c,v,d,f to get and set the clipboard and primary selection.
* Mark text and middle-click also in other applications.
* For wayland under x:
  * `$ mutter --wayland --no-x11 --nested`
  * `$ XDG_SESSION_TYPE=wayland SDL_VIDEODRIVER=wayland ./<path_to_test_appl_binary>`
DS пре 2 година
родитељ
комит
ac5b9bc4ee

+ 46 - 0
include/SDL_clipboard.h

@@ -82,6 +82,52 @@ extern DECLSPEC char * SDLCALL SDL_GetClipboardText(void);
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void);
 
+/**
+ * Put UTF-8 text into the primary selection.
+ *
+ * \param text the text to store in the primary selection
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 2.25.0.
+ *
+ * \sa SDL_GetPrimarySelectionText
+ * \sa SDL_HasPrimarySelectionText
+ */
+extern DECLSPEC int SDLCALL SDL_SetPrimarySelectionText(const char *text);
+
+/**
+ * Get UTF-8 text from the primary selection, which must be freed with SDL_free().
+ *
+ * This functions returns empty string if there was not enough memory left for
+ * a copy of the primary selection's content.
+ *
+ * \returns the primary selection text on success or an empty string on failure;
+ *          call SDL_GetError() for more information. Caller must call SDL_free()
+ *          on the returned pointer when done with it (even if there was an
+ *          error).
+ *
+ * \since This function is available since SDL 2.25.0.
+ *
+ * \sa SDL_HasPrimarySelectionText
+ * \sa SDL_SetPrimarySelectionText
+ */
+extern DECLSPEC char * SDLCALL SDL_GetPrimarySelectionText(void);
+
+/**
+ * Query whether the primary selection exists and contains a non-empty text
+ * string.
+ *
+ * \returns SDL_TRUE if the primary selection has text, or SDL_FALSE if it does
+ *          not.
+ *
+ * \since This function is available since SDL 2.25.0.
+ *
+ * \sa SDL_GetPrimarySelectionText
+ * \sa SDL_SetPrimarySelectionText
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasPrimarySelectionText(void);
+
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus

+ 1 - 1
include/SDL_events.h

@@ -143,7 +143,7 @@ typedef enum
     SDL_MULTIGESTURE,
 
     /* Clipboard events */
-    SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard changed */
+    SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard or primary selection changed */
 
     /* Drag and drop events */
     SDL_DROPFILE        = 0x1000, /**< The system requests a file open */

+ 3 - 0
src/dynapi/SDL2.exports

@@ -860,3 +860,6 @@
 ++'_SDL_crc16'.'SDL2.dll'.'SDL_crc16'
 ++'_SDL_GetWindowSizeInPixels'.'SDL2.dll'.'SDL_GetWindowSizeInPixels'
 ++'_SDL_GetJoystickGUIDInfo'.'SDL2.dll'.'SDL_GetJoystickGUIDInfo'
+++'_SDL_SetPrimarySelectionText'.'SDL2.dll'.'SDL_SetPrimarySelectionText'
+++'_SDL_GetPrimarySelectionText'.'SDL2.dll'.'SDL_GetPrimarySelectionText'
+++'_SDL_HasPrimarySelectionText'.'SDL2.dll'.'SDL_HasPrimarySelectionText'

+ 3 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -886,3 +886,6 @@
 #define SDL_crc16 SDL_crc16_REAL
 #define SDL_GetWindowSizeInPixels SDL_GetWindowSizeInPixels_REAL
 #define SDL_GetJoystickGUIDInfo SDL_GetJoystickGUIDInfo_REAL
+#define SDL_SetPrimarySelectionText SDL_SetPrimarySelectionText_REAL
+#define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL
+#define SDL_HasPrimarySelectionText SDL_HasPrimarySelectionText_REAL

+ 3 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -969,3 +969,6 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ResetHint,(const char *a),(a),return)
 SDL_DYNAPI_PROC(Uint16,SDL_crc16,(Uint16 a, const void *b, size_t c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_GetWindowSizeInPixels,(SDL_Window *a, int *b, int *c),(a,b,c),)
 SDL_DYNAPI_PROC(void,SDL_GetJoystickGUIDInfo,(SDL_JoystickGUID a, Uint16 *b, Uint16 *c, Uint16 *d, Uint16 *e),(a,b,c,d,e),)
+SDL_DYNAPI_PROC(int,SDL_SetPrimarySelectionText,(const char *a),(a),return)
+SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasPrimarySelectionText,(void),(),return)

+ 64 - 0
src/video/SDL_clipboard.c

@@ -45,6 +45,27 @@ SDL_SetClipboardText(const char *text)
     }
 }
 
+int
+SDL_SetPrimarySelectionText(const char *text)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (!_this) {
+        return SDL_SetError("Video subsystem must be initialized to set primary selection text");
+    }
+
+    if (!text) {
+        text = "";
+    }
+    if (_this->SetPrimarySelectionText) {
+        return _this->SetPrimarySelectionText(_this, text);
+    } else {
+        SDL_free(_this->primary_selection_text);
+        _this->primary_selection_text = SDL_strdup(text);
+        return 0;
+    }
+}
+
 char *
 SDL_GetClipboardText(void)
 {
@@ -66,6 +87,27 @@ SDL_GetClipboardText(void)
     }
 }
 
+char *
+SDL_GetPrimarySelectionText(void)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (!_this) {
+        SDL_SetError("Video subsystem must be initialized to get primary selection text");
+        return SDL_strdup("");
+    }
+
+    if (_this->GetPrimarySelectionText) {
+        return _this->GetPrimarySelectionText(_this);
+    } else {
+        const char *text = _this->primary_selection_text;
+        if (!text) {
+            text = "";
+        }
+        return SDL_strdup(text);
+    }
+}
+
 SDL_bool
 SDL_HasClipboardText(void)
 {
@@ -87,4 +129,26 @@ SDL_HasClipboardText(void)
     }
 }
 
+SDL_bool
+SDL_HasPrimarySelectionText(void)
+{
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+
+    if (!_this) {
+        SDL_SetError("Video subsystem must be initialized to check primary selection text");
+        return SDL_FALSE;
+    }
+
+    if (_this->HasPrimarySelectionText) {
+        return _this->HasPrimarySelectionText(_this);
+    } else {
+        if (_this->primary_selection_text && _this->primary_selection_text[0] != '\0') {
+            return SDL_TRUE;
+        } else {
+            return SDL_FALSE;
+        }
+    }
+    return SDL_FALSE;
+}
+
 /* vi: set ts=4 sw=4 expandtab: */

+ 6 - 2
src/video/SDL_sysvideo.h

@@ -328,6 +328,9 @@ struct SDL_VideoDevice
     int (*SetClipboardText) (_THIS, const char *text);
     char * (*GetClipboardText) (_THIS);
     SDL_bool (*HasClipboardText) (_THIS);
+    int (*SetPrimarySelectionText) (_THIS, const char *text);
+    char * (*GetPrimarySelectionText) (_THIS);
+    SDL_bool (*HasPrimarySelectionText) (_THIS);
 
     /* MessageBox */
     int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid);
@@ -353,6 +356,7 @@ struct SDL_VideoDevice
     Uint8 window_magic;
     Uint32 next_object_id;
     char *clipboard_text;
+    char *primary_selection_text;
     SDL_bool setting_display_mode;
     Uint32 quirk_flags;
 
@@ -422,11 +426,11 @@ struct SDL_VideoDevice
     /* Data private to this driver */
     void *driverdata;
     struct SDL_GLDriverData *gl_data;
-    
+
 #if SDL_VIDEO_OPENGL_EGL
     struct SDL_EGL_VideoData *egl_data;
 #endif
-    
+
 #if SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
     struct SDL_PrivateGLESData *gles_data;
 #endif

+ 101 - 20
src/video/wayland/SDL_waylandclipboard.c

@@ -58,6 +58,39 @@ Wayland_SetClipboardText(_THIS, const char *text)
     return status;
 }
 
+int
+Wayland_SetPrimarySelectionText(_THIS, const char *text)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+
+    int status = 0;
+
+    if (_this == NULL || _this->driverdata == NULL) {
+        status = SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) {
+            primary_selection_device = video_data->input->primary_selection_device;
+            if (text[0] != '\0') {
+                SDL_WaylandPrimarySelectionSource* source = Wayland_primary_selection_source_create(_this);
+                Wayland_primary_selection_source_add_data(source, TEXT_MIME, text,
+                                                          SDL_strlen(text));
+
+                status = Wayland_primary_selection_device_set_selection(primary_selection_device,
+                                                                        source);
+                if (status != 0) {
+                    Wayland_primary_selection_source_destroy(source);
+                }
+            } else {
+                status = Wayland_primary_selection_device_clear_selection(primary_selection_device);
+            }
+        }
+    }
+
+    return status;
+}
+
 char *
 Wayland_GetClipboardText(_THIS)
 {
@@ -65,8 +98,6 @@ Wayland_GetClipboardText(_THIS)
     SDL_WaylandDataDevice *data_device = NULL;
 
     char *text = NULL;
-
-    void *buffer = NULL;
     size_t length = 0;
 
     if (_this == NULL || _this->driverdata == NULL) {
@@ -75,19 +106,50 @@ Wayland_GetClipboardText(_THIS)
         video_data = _this->driverdata;
         if (video_data->input != NULL && video_data->input->data_device != NULL) {
             data_device = video_data->input->data_device;
-            if (data_device->selection_offer != NULL) {
-                buffer = Wayland_data_offer_receive(data_device->selection_offer,
+            /* Prefer own selection, if not canceled */
+            if (Wayland_data_source_has_mime(
+                    data_device->selection_source, TEXT_MIME)) {
+                text = Wayland_data_source_get_data(data_device->selection_source,
                                                     &length, TEXT_MIME, SDL_TRUE);
-                if (length > 0) {
-                    text = (char*) buffer;
-                }
+            } else if (Wayland_data_offer_has_mime(
+                    data_device->selection_offer, TEXT_MIME)) {
+                text = Wayland_data_offer_receive(data_device->selection_offer,
+                                                  &length, TEXT_MIME, SDL_TRUE);
             }
-            if (length == 0 && 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;
+}
+
+char *
+Wayland_GetPrimarySelectionText(_THIS)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+
+    char *text = NULL;
+    size_t length = 0;
+
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) {
+            primary_selection_device = video_data->input->primary_selection_device;
+            /* Prefer own selection, if not canceled */
+            if (Wayland_primary_selection_source_has_mime(
+                    primary_selection_device->selection_source, TEXT_MIME)) {
+                text = Wayland_primary_selection_source_get_data(primary_selection_device->selection_source,
+                                                                 &length, TEXT_MIME, SDL_TRUE);
+            } else if (Wayland_primary_selection_offer_has_mime(
+                    primary_selection_device->selection_offer, TEXT_MIME)) {
+                text = Wayland_primary_selection_offer_receive(primary_selection_device->selection_offer,
+                                                               &length, TEXT_MIME, SDL_TRUE);
             }
         }
     }
@@ -112,13 +174,32 @@ Wayland_HasClipboardText(_THIS)
         video_data = _this->driverdata;
         if (video_data->input != NULL && video_data->input->data_device != NULL) {
             data_device = video_data->input->data_device;
-            if (Wayland_data_offer_has_mime(
-                    data_device->selection_offer, TEXT_MIME)) {
-                result = SDL_TRUE;
-            } else if (Wayland_data_source_has_mime(
-                    data_device->selection_source, TEXT_MIME)) {
-                result = SDL_TRUE;
-            }
+            result = result ||
+                     Wayland_data_source_has_mime(data_device->selection_source, TEXT_MIME) ||
+                     Wayland_data_offer_has_mime(data_device->selection_offer, TEXT_MIME);
+        }
+    }
+    return result;
+}
+
+SDL_bool
+Wayland_HasPrimarySelectionText(_THIS)
+{
+    SDL_VideoData *video_data = NULL;
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+
+    SDL_bool result = SDL_FALSE;
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        video_data = _this->driverdata;
+        if (video_data->input != NULL && video_data->input->primary_selection_device != NULL) {
+            primary_selection_device = video_data->input->primary_selection_device;
+            result = result ||
+                     Wayland_primary_selection_source_has_mime(
+                         primary_selection_device->selection_source, TEXT_MIME) ||
+                     Wayland_primary_selection_offer_has_mime(
+                         primary_selection_device->selection_offer, TEXT_MIME);
         }
     }
     return result;

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

@@ -26,6 +26,9 @@
 extern int Wayland_SetClipboardText(_THIS, const char *text);
 extern char *Wayland_GetClipboardText(_THIS);
 extern SDL_bool Wayland_HasClipboardText(_THIS);
+extern int Wayland_SetPrimarySelectionText(_THIS, const char *text);
+extern char *Wayland_GetPrimarySelectionText(_THIS);
+extern SDL_bool Wayland_HasPrimarySelectionText(_THIS);
 
 #endif /* SDL_waylandclipboard_h_ */
 

+ 289 - 54
src/video/wayland/SDL_waylanddatamanager.c

@@ -33,11 +33,12 @@
 
 #include "SDL_waylandvideo.h"
 #include "SDL_waylanddatamanager.h"
+#include "primary-selection-unstable-v1-client-protocol.h"
 
 /* FIXME: This is arbitrary, but we want this to be less than a frame because
  * any longer can potentially spin an infinite loop of PumpEvents (!)
  */
-#define PIPE_MS_TIMEOUT 10
+#define PIPE_MS_TIMEOUT 14
 
 static ssize_t
 write_pipe(int fd, const void* buffer, size_t total_length, size_t *pos)
@@ -53,7 +54,7 @@ write_pipe(int fd, const void* buffer, size_t total_length, size_t *pos)
     ready = SDL_IOReady(fd, SDL_IOR_WRITE, PIPE_MS_TIMEOUT);
 
     sigemptyset(&sig_set);
-    sigaddset(&sig_set, SIGPIPE);  
+    sigaddset(&sig_set, SIGPIPE);
 
 #if SDL_THREADS_DISABLED
     sigprocmask(SIG_BLOCK, &sig_set, &old_sig_set);
@@ -97,7 +98,7 @@ read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate)
     size_t pos = 0;
 
     ready = SDL_IOReady(fd, SDL_IOR_READ, PIPE_MS_TIMEOUT);
-  
+
     if (ready == 0) {
         bytes_read = SDL_SetError("Pipe timeout");
     } else if (ready < 0) {
@@ -120,8 +121,8 @@ read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate)
             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 {
@@ -130,7 +131,7 @@ read_pipe(int fd, void** buffer, size_t* total_length, SDL_bool null_terminate)
             if (null_terminate == SDL_TRUE) {
                 SDL_memset((Uint8*)output_buffer + (new_buffer_length - 1), 0, 1);
             }
-            
+
             *buffer = output_buffer;
         }
     }
@@ -160,28 +161,28 @@ Wayland_convert_mime_type(const char *mime_type)
             break;
         }
     }
-    
+
     return found;
 }
 
 static SDL_MimeDataList*
-mime_data_list_find(struct wl_list* list, 
+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) { 
+    wl_list_for_each(mime_list, list, link) {
         if (SDL_strcmp(mime_list->mime_type, mime_type) == 0) {
             found = mime_list;
             break;
         }
-    }    
+    }
     return found;
 }
 
 static int
-mime_data_list_add(struct wl_list* list, 
+mime_data_list_add(struct wl_list* list,
                    const char* mime_type,
                    const void* buffer, size_t length)
 {
@@ -216,7 +217,7 @@ mime_data_list_add(struct wl_list* list,
             }
         }
     }
-    
+
     if (mime_data != NULL && buffer != NULL && length > 0) {
         if (mime_data->data != NULL) {
             SDL_free(mime_data->data);
@@ -233,31 +234,25 @@ mime_data_list_add(struct wl_list* list,
 static void
 mime_data_list_free(struct wl_list *list)
 {
-    SDL_MimeDataList *mime_data = NULL; 
+    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);       
-    } 
+        SDL_free(mime_data);
+    }
 }
 
-ssize_t 
-Wayland_data_source_send(SDL_WaylandDataSource *source,  
-                         const char *mime_type, int fd)
+static ssize_t
+Wayland_source_send(SDL_MimeDataList *mime_data, 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");
@@ -271,15 +266,49 @@ Wayland_data_source_send(SDL_WaylandDataSource *source,
     return status;
 }
 
+ssize_t
+Wayland_data_source_send(SDL_WaylandDataSource *source,
+                         const char *mime_type, int fd)
+{
+    SDL_MimeDataList *mime_data = NULL;
+
+    mime_type = Wayland_convert_mime_type(mime_type);
+    mime_data = mime_data_list_find(&source->mimes,
+                                                      mime_type);
+
+    return Wayland_source_send(mime_data, mime_type, fd);
+}
+
+ssize_t
+Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source,
+                                      const char *mime_type, int fd)
+{
+    SDL_MimeDataList *mime_data = NULL;
+
+    mime_type = Wayland_convert_mime_type(mime_type);
+    mime_data = mime_data_list_find(&source->mimes,
+                                                      mime_type);
+
+    return Wayland_source_send(mime_data, mime_type, fd);
+}
+
 int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
                                  const char *mime_type,
                                  const void *buffer,
-                                 size_t length) 
+                                 size_t length)
+{
+    return mime_data_list_add(&source->mimes, mime_type, buffer, length);
+}
+
+int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *source,
+                                              const char *mime_type,
+                                              const void *buffer,
+                                              size_t length)
 {
     return mime_data_list_add(&source->mimes, mime_type, buffer, length);
 }
 
-SDL_bool 
+SDL_bool
 Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
                              const char *mime_type)
 {
@@ -291,7 +320,47 @@ Wayland_data_source_has_mime(SDL_WaylandDataSource *source,
     return found;
 }
 
-void* 
+SDL_bool
+Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *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;
+}
+
+static void*
+Wayland_source_get_data(SDL_MimeDataList *mime_data,
+                        size_t *length,
+                        SDL_bool null_terminate)
+{
+    void *buffer = NULL;
+
+    if (mime_data != NULL && mime_data->length > 0) {
+        size_t buffer_length = mime_data->length;
+
+        if (null_terminate == SDL_TRUE) {
+            ++buffer_length;
+        }
+        buffer = SDL_malloc(buffer_length);
+        if (buffer == NULL) {
+            *length = SDL_OutOfMemory();
+        } else {
+            *length = mime_data->length;
+            SDL_memcpy(buffer, mime_data->data, mime_data->length);
+            if (null_terminate) {
+                *((Uint8 *)buffer + mime_data->length) = 0;
+            }
+        }
+    }
+
+    return buffer;
+}
+
+void*
 Wayland_data_source_get_data(SDL_WaylandDataSource *source,
                              size_t *length, const char* mime_type,
                              SDL_bool null_terminate)
@@ -304,23 +373,26 @@ Wayland_data_source_get_data(SDL_WaylandDataSource *source,
         SDL_SetError("Invalid data source");
     } else {
         mime_data = mime_data_list_find(&source->mimes, mime_type);
-        if (mime_data != NULL && mime_data->length > 0) {
-            size_t buffer_length = mime_data->length;
+        buffer = Wayland_source_get_data(mime_data, length, null_terminate);
+    }
 
-            if (null_terminate == SDL_TRUE) {
-                ++buffer_length;
-            }
-            buffer = SDL_malloc(buffer_length);
-            if (buffer == NULL) {
-                *length = SDL_OutOfMemory();
-            } else {
-                *length = mime_data->length;
-                SDL_memcpy(buffer, mime_data->data, mime_data->length);
-                if (null_terminate) {
-                    *((Uint8 *)buffer + mime_data->length) = 0;
-                }
-            }
-        }
+    return buffer;
+}
+
+void*
+Wayland_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSource *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 primary selection source");
+    } else {
+        mime_data = mime_data_list_find(&source->mimes, mime_type);
+        buffer = Wayland_source_get_data(mime_data, length, null_terminate);
     }
 
     return buffer;
@@ -340,13 +412,27 @@ Wayland_data_source_destroy(SDL_WaylandDataSource *source)
     }
 }
 
-void* 
+void
+Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source)
+{
+    if (source != NULL) {
+        SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *) source->primary_selection_device;
+        if (primary_selection_device && (primary_selection_device->selection_source == source)) {
+            primary_selection_device->selection_source = NULL;
+        }
+        zwp_primary_selection_source_v1_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;
@@ -364,22 +450,59 @@ Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
         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 
+void*
+Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *offer,
+                                        size_t *length, const char* mime_type,
+                                        SDL_bool null_terminate)
+{
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+
+    int pipefd[2];
+    void *buffer = NULL;
+    *length = 0;
+
+    if (offer == NULL) {
+        SDL_SetError("Invalid data offer");
+    } else if ((primary_selection_device = offer->primary_selection_device) == NULL) {
+        SDL_SetError("Primary selection device not initialized");
+    } else if (pipe2(pipefd, O_CLOEXEC|O_NONBLOCK) == -1) {
+        SDL_SetError("Could not read pipe");
+    } else {
+        zwp_primary_selection_offer_v1_receive(offer->offer, mime_type, pipefd[1]);
+
+        /* TODO: Needs pump and flush? */
+        WAYLAND_wl_display_flush(primary_selection_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);
 }
 
+int
+Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer,
+                                         const char* mime_type)
+{
+    return mime_data_list_add(&offer->mimes, mime_type, NULL, 0);
+}
 
-SDL_bool 
+SDL_bool
 Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
                             const char *mime_type)
 {
@@ -391,6 +514,18 @@ Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer,
     return found;
 }
 
+SDL_bool
+Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *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)
 {
@@ -401,6 +536,16 @@ Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer)
     }
 }
 
+void
+Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer)
+{
+    if (offer != NULL) {
+        zwp_primary_selection_offer_v1_destroy(offer->offer);
+        mime_data_list_free(&offer->mimes);
+        SDL_free(offer);
+    }
+}
+
 int
 Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
 {
@@ -416,6 +561,22 @@ Wayland_data_device_clear_selection(SDL_WaylandDataDevice *data_device)
     return status;
 }
 
+int
+Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device)
+{
+    int status = 0;
+
+    if (primary_selection_device == NULL || primary_selection_device->primary_selection_device == NULL) {
+        status = SDL_SetError("Invalid Primary Selection Device");
+    } else if (primary_selection_device->selection_source != NULL) {
+        zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
+                                                      NULL, 0);
+        Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
+        primary_selection_device->selection_source = NULL;
+    }
+    return status;
+}
+
 int
 Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
                                   SDL_WaylandDataSource *source)
@@ -433,7 +594,7 @@ Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
 
         wl_list_for_each(mime_data, &(source->mimes), link) {
             wl_data_source_offer(source->source,
-                                 mime_data->mime_type); 
+                                 mime_data->mime_type);
 
             /* TODO - Improve system for multiple mime types to same data */
             for (index = 0; index < MIME_LIST_SIZE; ++index) {
@@ -443,9 +604,9 @@ Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
                }
             }
             /* */
- 
+
             ++num_offers;
-        } 
+        }
 
         if (num_offers == 0) {
             Wayland_data_device_clear_selection(data_device);
@@ -455,7 +616,7 @@ Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
             if (data_device->selection_serial != 0) {
                 wl_data_device_set_selection(data_device->data_device,
                                              source->source,
-                                             data_device->selection_serial); 
+                                             data_device->selection_serial);
             }
             if (data_device->selection_source != NULL) {
                 Wayland_data_source_destroy(data_device->selection_source);
@@ -468,6 +629,58 @@ Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device,
     return status;
 }
 
+int
+Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
+                                              SDL_WaylandPrimarySelectionSource *source)
+{
+    int status = 0;
+    size_t num_offers = 0;
+    size_t index = 0;
+
+    if (primary_selection_device == NULL) {
+        status = SDL_SetError("Invalid Primary Selection 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) {
+            zwp_primary_selection_source_v1_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 (SDL_strcmp(mime_conversion_list[index][1], mime_data->mime_type) == 0) {
+                zwp_primary_selection_source_v1_offer(source->source,
+                                                      mime_conversion_list[index][0]);
+               }
+            }
+            /* */
+
+            ++num_offers;
+        }
+
+        if (num_offers == 0) {
+            Wayland_primary_selection_device_clear_selection(primary_selection_device);
+            status = SDL_SetError("No mime data");
+        } else {
+            /* 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);
+            }
+            if (primary_selection_device->selection_source != NULL) {
+                Wayland_primary_selection_source_destroy(primary_selection_device->selection_source);
+            }
+            primary_selection_device->selection_source = source;
+            source->primary_selection_device = primary_selection_device;
+        }
+    }
+
+    return status;
+}
+
 int
 Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device,
                                uint32_t serial)
@@ -481,13 +694,35 @@ Wayland_data_device_set_serial(SDL_WaylandDataDevice *data_device,
             && data_device->selection_source != NULL) {
             wl_data_device_set_selection(data_device->data_device,
                                          data_device->selection_source->source,
-                                         serial); 
+                                         data_device->selection_serial);
         }
 
         data_device->selection_serial = serial;
     }
 
-    return status; 
+    return status;
+}
+
+int
+Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *primary_selection_device,
+                                            uint32_t serial)
+{
+    int status = -1;
+    if (primary_selection_device != NULL) {
+        status = 0;
+
+        /* 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 != NULL) {
+            zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device,
+                                                          primary_selection_device->selection_source->source,
+                                                          primary_selection_device->selection_serial);
+        }
+
+        primary_selection_device->selection_serial = serial;
+    }
+
+    return status;
 }
 
 #endif /* SDL_VIDEO_DRIVER_WAYLAND */

+ 56 - 7
src/video/wayland/SDL_waylanddatamanager.h

@@ -43,12 +43,24 @@ typedef struct {
     void *data_device;
 } SDL_WaylandDataSource;
 
+typedef struct {
+    struct zwp_primary_selection_source_v1 *source;
+    struct wl_list mimes;
+    void *primary_selection_device;
+} SDL_WaylandPrimarySelectionSource;
+
 typedef struct {
     struct wl_data_offer *offer;
     struct wl_list mimes;
     void *data_device;
 } SDL_WaylandDataOffer;
 
+typedef struct {
+    struct zwp_primary_selection_offer_v1 *offer;
+    struct wl_list mimes;
+    void *primary_selection_device;
+} SDL_WaylandPrimarySelectionOffer;
+
 typedef struct {
     struct wl_data_device *data_device;
     SDL_VideoData *video_data;
@@ -58,46 +70,83 @@ typedef struct {
     SDL_WaylandDataOffer *drag_offer;
     SDL_WaylandDataOffer *selection_offer;
 
-    /* Clipboard */
+    /* Clipboard and Primary Selection */
     uint32_t selection_serial;
     SDL_WaylandDataSource *selection_source;
 } SDL_WaylandDataDevice;
 
+typedef struct {
+    struct zwp_primary_selection_device_v1 *primary_selection_device;
+    SDL_VideoData *video_data;
+
+    uint32_t selection_serial;
+    SDL_WaylandPrimarySelectionSource *selection_source;
+    SDL_WaylandPrimarySelectionOffer *selection_offer;
+} SDL_WaylandPrimarySelectionDevice;
+
 extern const char* Wayland_convert_mime_type(const char *mime_type);
 
-/* Wayland Data Source - (Sending) */
+/* Wayland Data Source / Primary Selection Source - (Sending) */
 extern SDL_WaylandDataSource* Wayland_data_source_create(_THIS);
-extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, 
+extern SDL_WaylandPrimarySelectionSource* Wayland_primary_selection_source_create(_THIS);
+extern ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source,
                                         const char *mime_type, int fd);
+extern ssize_t Wayland_primary_selection_source_send(SDL_WaylandPrimarySelectionSource *source,
+                                                     const char *mime_type, int fd);
 extern int Wayland_data_source_add_data(SDL_WaylandDataSource *source,
-                                        const char *mime_type, 
-                                        const void *buffer, 
+                                        const char *mime_type,
+                                        const void *buffer,
                                         size_t length);
+extern int Wayland_primary_selection_source_add_data(SDL_WaylandPrimarySelectionSource *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 SDL_bool Wayland_primary_selection_source_has_mime(SDL_WaylandPrimarySelectionSource *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_primary_selection_source_get_data(SDL_WaylandPrimarySelectionSource *source,
+                                                       size_t *length,
+                                                       const char *mime_type,
+                                                       SDL_bool null_terminate);
 extern void Wayland_data_source_destroy(SDL_WaylandDataSource *source);
+extern void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource *source);
 
-/* Wayland Data Offer - (Receiving) */
+/* Wayland Data / Primary Selection Offer - (Receiving) */
 extern void* Wayland_data_offer_receive(SDL_WaylandDataOffer *offer,
                                         size_t *length,
                                         const char *mime_type,
                                         SDL_bool null_terminate);
+extern void* Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelectionOffer *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 SDL_bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer,
+                                                         const char *mime_type);
 extern int Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer,
                                        const char *mime_type);
+extern int Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer,
+                                                    const char *mime_type);
 extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer);
+extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer);
 
-/* Clipboard */
+/* Clipboard / Primary Selection */
 extern int Wayland_data_device_clear_selection(SDL_WaylandDataDevice *device);
+extern int Wayland_primary_selection_device_clear_selection(SDL_WaylandPrimarySelectionDevice *device);
 extern int Wayland_data_device_set_selection(SDL_WaylandDataDevice *device,
                                              SDL_WaylandDataSource *source);
+extern int Wayland_primary_selection_device_set_selection(SDL_WaylandPrimarySelectionDevice *device,
+                                             SDL_WaylandPrimarySelectionSource *source);
 extern int Wayland_data_device_set_serial(SDL_WaylandDataDevice *device,
                                           uint32_t serial);
+extern int Wayland_primary_selection_device_set_serial(SDL_WaylandPrimarySelectionDevice *device,
+                                                       uint32_t serial);
 #endif /* SDL_waylanddatamanager_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 152 - 2
src/video/wayland/SDL_waylandevents.c

@@ -42,6 +42,7 @@
 #include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
 #include "text-input-unstable-v3-client-protocol.h"
 #include "tablet-unstable-v2-client-protocol.h"
+#include "primary-selection-unstable-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -587,6 +588,7 @@ pointer_handle_button_common(struct SDL_WaylandInput *input, uint32_t serial,
         }
 
         Wayland_data_device_set_serial(input->data_device, serial);
+        Wayland_primary_selection_device_set_serial(input->primary_selection_device, serial);
 
         SDL_SendMouseButton(window->sdlwindow, 0,
                             state ? SDL_PRESSED : SDL_RELEASED, sdl_button);
@@ -1106,6 +1108,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
         if (has_text && !(SDL_GetModState() & KMOD_CTRL)) {
             Wayland_data_device_set_serial(input->data_device, serial);
+            Wayland_primary_selection_device_set_serial(input->primary_selection_device, serial);
             if (!handled_by_ime) {
                 SDL_SendKeyboardText(text);
             }
@@ -1319,6 +1322,25 @@ static const struct wl_data_source_listener data_source_listener = {
     data_source_handle_action,             // Version 3
 };
 
+static void
+primary_selection_source_send(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
+		                      const char *mime_type, int32_t fd)
+{
+    Wayland_primary_selection_source_send((SDL_WaylandPrimarySelectionSource *)data,
+                                          mime_type, fd);
+}
+
+static void
+primary_selection_source_cancelled(void *data, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+    Wayland_primary_selection_source_destroy(data);
+}
+
+static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
+    primary_selection_source_send,
+    primary_selection_source_cancelled,
+};
+
 SDL_WaylandDataSource*
 Wayland_data_source_create(_THIS)
 {
@@ -1355,6 +1377,41 @@ Wayland_data_source_create(_THIS)
     return data_source;
 }
 
+SDL_WaylandPrimarySelectionSource*
+Wayland_primary_selection_source_create(_THIS)
+{
+    SDL_WaylandPrimarySelectionSource *primary_selection_source = NULL;
+    SDL_VideoData *driver_data = NULL;
+    struct zwp_primary_selection_source_v1 *id = NULL;
+
+    if (_this == NULL || _this->driverdata == NULL) {
+        SDL_SetError("Video driver uninitialized");
+    } else {
+        driver_data = _this->driverdata;
+
+        if (driver_data->primary_selection_device_manager != NULL) {
+            id = zwp_primary_selection_device_manager_v1_create_source(
+                     driver_data->primary_selection_device_manager);
+        }
+
+        if (id == NULL) {
+            SDL_SetError("Wayland unable to create primary selection source");
+        } else {
+            primary_selection_source = SDL_calloc(1, sizeof *primary_selection_source);
+            if (primary_selection_source == NULL) {
+                SDL_OutOfMemory();
+                zwp_primary_selection_source_v1_destroy(id);
+            } else {
+                WAYLAND_wl_list_init(&(primary_selection_source->mimes));
+                primary_selection_source->source = id;
+                zwp_primary_selection_source_v1_add_listener(id, &primary_selection_source_listener,
+                        primary_selection_source);
+            }
+        }
+    }
+    return primary_selection_source;
+}
+
 static void
 data_offer_handle_offer(void *data, struct wl_data_offer *wl_data_offer,
                         const char *mime_type)
@@ -1381,6 +1438,18 @@ static const struct wl_data_offer_listener data_offer_listener = {
     data_offer_handle_actions,        // Version 3
 };
 
+static void
+primary_selection_offer_handle_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
+                                     const char *mime_type)
+{
+    SDL_WaylandPrimarySelectionOffer *offer = data;
+    Wayland_primary_selection_offer_add_mime(offer, mime_type);
+}
+
+static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
+    primary_selection_offer_handle_offer,
+};
+
 static void
 data_device_handle_data_offer(void *data, struct wl_data_device *wl_data_device,
                               struct wl_data_offer *id)
@@ -1620,6 +1689,48 @@ static const struct wl_data_device_listener data_device_listener = {
     data_device_handle_selection
 };
 
+static void
+primary_selection_device_handle_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+                                      struct zwp_primary_selection_offer_v1 *id)
+{
+    SDL_WaylandPrimarySelectionOffer *primary_selection_offer = NULL;
+
+    primary_selection_offer = SDL_calloc(1, sizeof *primary_selection_offer);
+    if (primary_selection_offer == NULL) {
+        SDL_OutOfMemory();
+    } else {
+        primary_selection_offer->offer = id;
+        primary_selection_offer->primary_selection_device = data;
+        WAYLAND_wl_list_init(&(primary_selection_offer->mimes));
+        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);
+    }
+}
+
+static void
+primary_selection_device_handle_selection(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+                                          struct zwp_primary_selection_offer_v1 *id)
+{
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = data;
+    SDL_WaylandPrimarySelectionOffer *offer = NULL;
+
+    if (id != NULL) {
+        offer = zwp_primary_selection_offer_v1_get_user_data(id);
+    }
+
+    if (primary_selection_device->selection_offer != offer) {
+        Wayland_primary_selection_offer_destroy(primary_selection_device->selection_offer);
+        primary_selection_device->selection_offer = offer;
+    }
+
+    SDL_SendClipboardUpdate();
+}
+
+static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
+    primary_selection_device_handle_offer,
+    primary_selection_device_handle_selection
+};
+
 static void
 text_input_enter(void *data,
                  struct zwp_text_input_v3 *zwp_text_input_v3,
@@ -1667,9 +1778,9 @@ text_input_preedit_string(void *data,
             do {
                 const int sz = (int)SDL_utf8strlcpy(buf, text+i, sizeof(buf));
                 const int chars = (int)SDL_utf8strlen(buf);
-    
+
                 SDL_SendEditingText(buf, cursor, chars);
-    
+
                 i += sz;
                 cursor += chars;
             } while (i < text_bytes);
@@ -1753,6 +1864,32 @@ Wayland_create_data_device(SDL_VideoData *d)
     }
 }
 
+static void
+Wayland_create_primary_selection_device(SDL_VideoData *d)
+{
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device = NULL;
+
+    primary_selection_device = SDL_calloc(1, sizeof *primary_selection_device);
+    if (primary_selection_device == NULL) {
+        return;
+    }
+
+    primary_selection_device->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
+        d->primary_selection_device_manager, d->input->seat
+    );
+    primary_selection_device->video_data = d;
+
+    if (primary_selection_device->primary_selection_device == NULL) {
+        SDL_free(primary_selection_device);
+    } else {
+        zwp_primary_selection_device_v1_set_user_data(primary_selection_device->primary_selection_device,
+                                                      primary_selection_device);
+        zwp_primary_selection_device_v1_add_listener(primary_selection_device->primary_selection_device,
+                                                     &primary_selection_device_listener, primary_selection_device);
+        d->input->primary_selection_device = primary_selection_device;
+    }
+}
+
 static void
 Wayland_create_text_input(SDL_VideoData *d)
 {
@@ -1787,6 +1924,16 @@ Wayland_add_data_device_manager(SDL_VideoData *d, uint32_t id, uint32_t version)
     }
 }
 
+void
+Wayland_add_primary_selection_device_manager(SDL_VideoData *d, uint32_t id, uint32_t version)
+{
+    d->primary_selection_device_manager = wl_registry_bind(d->registry, id, &zwp_primary_selection_device_manager_v1_interface, 1);
+
+    if (d->input != NULL) {
+        Wayland_create_primary_selection_device(d);
+    }
+}
+
 void
 Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32_t version)
 {
@@ -2173,6 +2320,9 @@ Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version)
     if (d->data_device_manager != NULL) {
         Wayland_create_data_device(d);
     }
+    if (d->primary_selection_device_manager != NULL) {
+        Wayland_create_primary_selection_device(d);
+    }
     if (d->text_input_manager != NULL) {
         Wayland_create_text_input(d);
     }

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

@@ -85,6 +85,7 @@ struct SDL_WaylandInput {
     struct wl_touch *touch;
     struct wl_keyboard *keyboard;
     SDL_WaylandDataDevice *data_device;
+    SDL_WaylandPrimarySelectionDevice *primary_selection_device;
     SDL_WaylandTextInput *text_input;
     struct zwp_relative_pointer_v1 *relative_pointer;
     SDL_WindowData *pointer_focus;
@@ -137,6 +138,7 @@ extern void Wayland_SendWakeupEvent(_THIS, SDL_Window *window);
 extern int Wayland_WaitEventTimeout(_THIS, int timeout);
 
 extern void Wayland_add_data_device_manager(SDL_VideoData *d, uint32_t id, uint32_t version);
+extern void Wayland_add_primary_selection_device_manager(SDL_VideoData *d, uint32_t id, uint32_t version);
 extern void Wayland_add_text_input_manager(SDL_VideoData *d, uint32_t id, uint32_t version);
 
 extern void Wayland_display_add_input(SDL_VideoData *d, uint32_t id, uint32_t version);

+ 12 - 1
src/video/wayland/SDL_waylandvideo.c

@@ -55,6 +55,8 @@
 #include "tablet-unstable-v2-client-protocol.h"
 #include "xdg-output-unstable-v1-client-protocol.h"
 #include "viewporter-client-protocol.h"
+#include "viewporter-client-protocol.h"
+#include "primary-selection-unstable-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -265,6 +267,9 @@ Wayland_CreateDevice(void)
     device->SetClipboardText = Wayland_SetClipboardText;
     device->GetClipboardText = Wayland_GetClipboardText;
     device->HasClipboardText = Wayland_HasClipboardText;
+    device->SetPrimarySelectionText = Wayland_SetPrimarySelectionText;
+    device->GetPrimarySelectionText = Wayland_GetPrimarySelectionText;
+    device->HasPrimarySelectionText = Wayland_HasPrimarySelectionText;
     device->StartTextInput = Wayland_StartTextInput;
     device->StopTextInput = Wayland_StopTextInput;
     device->SetTextInputRect = Wayland_SetTextInputRect;
@@ -868,6 +873,8 @@ display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
         Wayland_add_text_input_manager(d, id, version);
     } else if (SDL_strcmp(interface, "wl_data_device_manager") == 0) {
         Wayland_add_data_device_manager(d, id, version);
+    } else if (SDL_strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0) {
+        Wayland_add_primary_selection_device_manager(d, id, version);
     } else if (SDL_strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
         d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1);
     } else if (SDL_strcmp(interface, "zwp_tablet_manager_v2") == 0) {
@@ -908,7 +915,7 @@ static const struct wl_registry_listener registry_listener = {
     display_handle_global,
     display_remove_global
 };
- 
+
 #ifdef HAVE_LIBDECOR_H
 static SDL_bool should_use_libdecor(SDL_VideoData *data, SDL_bool ignore_xdg)
 {
@@ -1107,6 +1114,10 @@ Wayland_VideoQuit(_THIS)
         wp_viewporter_destroy(data->viewporter);
     }
 
+    if (data->primary_selection_device_manager) {
+        zwp_primary_selection_device_manager_v1_destroy(data->primary_selection_device_manager);
+    }
+
     if (data->compositor)
         wl_compositor_destroy(data->compositor);
 

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

@@ -68,6 +68,7 @@ typedef struct {
     struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
     struct zwp_pointer_constraints_v1 *pointer_constraints;
     struct wl_data_device_manager *data_device_manager;
+    struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager;
     struct zxdg_decoration_manager_v1 *decoration_manager;
     struct zwp_keyboard_shortcuts_inhibit_manager_v1 *key_inhibitor_manager;
     struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;

+ 69 - 30
src/video/x11/SDL_x11clipboard.c

@@ -52,11 +52,11 @@ GetWindow(_THIS)
     return data->clipboard_window;
 }
 
-
-/* We use our own cut-buffer for intermediate storage instead of  
-   XA_CUT_BUFFER0 because their use isn't really defined for holding UTF8. */ 
+/* We use our own cut-buffer for intermediate storage instead of
+   XA_CUT_BUFFER0 because their use isn't really defined for holding UTF8. */
 Atom
-X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType mime_type)
+X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType mime_type,
+        Atom selection_type)
 {
     switch (mime_type) {
         case SDL_X11_CLIPBOARD_MIME_TYPE_STRING:
@@ -65,7 +65,9 @@ X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType
         case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8:
         #endif
         case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT:
-            return X11_XInternAtom(display, "SDL_CUTBUFFER", False);
+            return X11_XInternAtom(display, selection_type == XA_PRIMARY ?
+                                   "SDL_CUTBUFFER_PRIMARY_SELECTION" : "SDL_CUTBUFFER",
+                                   False);
         default:
             SDL_SetError("Can't find mime_type.");
             return XA_STRING;
@@ -118,13 +120,11 @@ X11_GetSDLCutBufferClipboardInternalFormat(Display *display, enum ESDLX11Clipboa
     }
 }
 
-
-int
-X11_SetClipboardText(_THIS, const char *text)
+static int
+SetSelectionText(_THIS, const char *text, Atom selection_type)
 {
     Display *display = ((SDL_VideoData *) _this->driverdata)->display;
     Window window;
-    Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
 
     /* Get the SDL window that will own the selection */
     window = GetWindow(_this);
@@ -134,22 +134,19 @@ X11_SetClipboardText(_THIS, const char *text)
 
     /* Save the selection on the root window */
     X11_XChangeProperty(display, DefaultRootWindow(display),
-        X11_GetSDLCutBufferClipboardType(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING), X11_GetSDLCutBufferClipboardInternalFormat(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING), 8, PropModeReplace,
+        X11_GetSDLCutBufferClipboardType(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING, selection_type),
+        X11_GetSDLCutBufferClipboardInternalFormat(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING), 8, PropModeReplace,
         (const unsigned char *)text, SDL_strlen(text));
 
-    if (XA_CLIPBOARD != None &&
-        X11_XGetSelectionOwner(display, XA_CLIPBOARD) != window) {
-        X11_XSetSelectionOwner(display, XA_CLIPBOARD, window, CurrentTime);
+    if (X11_XGetSelectionOwner(display, selection_type) != window) {
+        X11_XSetSelectionOwner(display, selection_type, window, CurrentTime);
     }
 
-    if (X11_XGetSelectionOwner(display, XA_PRIMARY) != window) {
-        X11_XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime);
-    }
     return 0;
 }
 
-char *
-X11_GetClipboardText(_THIS)
+static char *
+GetSlectionText(_THIS, Atom selection_type)
 {
     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
     Display *display = videodata->display;
@@ -165,18 +162,13 @@ X11_GetClipboardText(_THIS)
     char *text;
     Uint32 waitStart;
     Uint32 waitElapsed;
-    Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
-    if (XA_CLIPBOARD == None) {
-        SDL_SetError("Couldn't access X clipboard");
-        return SDL_strdup("");
-    }
 
     text = NULL;
 
     /* Get the window that holds the selection */
     window = GetWindow(_this);
     format = X11_GetSDLCutBufferClipboardInternalFormat(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING);
-    owner = X11_XGetSelectionOwner(display, XA_CLIPBOARD);
+    owner = X11_XGetSelectionOwner(display, selection_type);
     if (owner == None) {
         /* Fall back to ancient X10 cut-buffers which do not support UTF8 strings*/
         owner = DefaultRootWindow(display);
@@ -184,12 +176,12 @@ X11_GetClipboardText(_THIS)
         format = XA_STRING;
     } else if (owner == window) {
         owner = DefaultRootWindow(display);
-        selection = X11_GetSDLCutBufferClipboardType(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING);
+        selection = X11_GetSDLCutBufferClipboardType(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING, selection_type);
     } else {
         /* Request that the selection owner copy the data to our window */
         owner = window;
         selection = X11_XInternAtom(display, "SDL_SELECTION", False);
-        X11_XConvertSelection(display, XA_CLIPBOARD, format, selection, owner,
+        X11_XConvertSelection(display, selection_type, format, selection, owner,
             CurrentTime);
 
         /* When using synergy on Linux and when data has been put in the clipboard
@@ -200,13 +192,13 @@ X11_GetClipboardText(_THIS)
         while (videodata->selection_waiting) {
             SDL_PumpEvents();
             waitElapsed = SDL_GetTicks() - waitStart;
-            /* Wait one second for a clipboard response. */
+            /* Wait one second for a selection response. */
             if (waitElapsed > 1000) {
                 videodata->selection_waiting = SDL_FALSE;
-                SDL_SetError("Clipboard timeout");
-                /* We need to set the clipboard text so that next time we won't
+                SDL_SetError("Selection timeout");
+                /* We need to set the selection text so that next time we won't
                    timeout, otherwise we will hang on every call to this function. */
-                X11_SetClipboardText(_this, "");
+                SetSelectionText(_this, "", selection_type);
                 return SDL_strdup("");
             }
         }
@@ -232,6 +224,41 @@ X11_GetClipboardText(_THIS)
     return text;
 }
 
+int
+X11_SetClipboardText(_THIS, const char *text)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
+    Atom XA_CLIPBOARD = X11_XInternAtom(videodata->display, "CLIPBOARD", 0);
+    if (XA_CLIPBOARD == None) {
+        return SDL_SetError("Couldn't access X clipboard");
+    }
+    return SetSelectionText(_this, text, XA_CLIPBOARD);
+}
+
+int
+X11_SetPrimarySelectionText(_THIS, const char *text)
+{
+    return SetSelectionText(_this, text, XA_PRIMARY);
+}
+
+char *
+X11_GetClipboardText(_THIS)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
+    Atom XA_CLIPBOARD = X11_XInternAtom(videodata->display, "CLIPBOARD", 0);
+    if (XA_CLIPBOARD == None) {
+        SDL_SetError("Couldn't access X clipboard");
+        return SDL_strdup("");
+    }
+    return GetSlectionText(_this, XA_CLIPBOARD);
+}
+
+char *
+X11_GetPrimarySelectionText(_THIS)
+{
+    return GetSlectionText(_this, XA_PRIMARY);
+}
+
 SDL_bool
 X11_HasClipboardText(_THIS)
 {
@@ -244,6 +271,18 @@ X11_HasClipboardText(_THIS)
     return result;
 }
 
+SDL_bool
+X11_HasPrimarySelectionText(_THIS)
+{
+    SDL_bool result = SDL_FALSE;
+    char *text = X11_GetPrimarySelectionText(_this);
+    if (text) {
+        result = text[0] != '\0' ? SDL_TRUE : SDL_FALSE;
+        SDL_free(text);
+    }
+    return result;
+}
+
 #endif /* SDL_VIDEO_DRIVER_X11 */
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 4 - 1
src/video/x11/SDL_x11clipboard.h

@@ -36,7 +36,10 @@ enum ESDLX11ClipboardMimeType {
 extern int X11_SetClipboardText(_THIS, const char *text);
 extern char *X11_GetClipboardText(_THIS);
 extern SDL_bool X11_HasClipboardText(_THIS);
-extern Atom X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType mime_type);
+extern int X11_SetPrimarySelectionText(_THIS, const char *text);
+extern char *X11_GetPrimarySelectionText(_THIS);
+extern SDL_bool X11_HasPrimarySelectionText(_THIS);
+extern Atom X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType mime_type, Atom selection_type);
 extern Atom X11_GetSDLCutBufferClipboardExternalFormat(Display *display, enum ESDLX11ClipboardMimeType mime_type);
 extern Atom X11_GetSDLCutBufferClipboardInternalFormat(Display *display, enum ESDLX11ClipboardMimeType mime_type);
 

+ 6 - 6
src/video/x11/SDL_x11events.c

@@ -620,7 +620,7 @@ X11_HandleClipboardEvent(_THIS, const XEvent *xevent)
             int seln_format, mime_formats;
             unsigned long nbytes;
             unsigned long overflow;
-            unsigned char *seln_data;            
+            unsigned char *seln_data;
             Atom supportedFormats[SDL_X11_CLIPBOARD_MIME_TYPE_MAX+1];
             Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
 
@@ -640,11 +640,11 @@ X11_HandleClipboardEvent(_THIS, const XEvent *xevent)
             /* !!! FIXME: We were probably storing this on the root window
                because an SDL window might go away...? but we don't have to do
                this now (or ever, really). */
-            
+
             if (req->target == XA_TARGETS) {
                 supportedFormats[0] = XA_TARGETS;
                 mime_formats = 1;
-                for (i = 0; i < SDL_X11_CLIPBOARD_MIME_TYPE_MAX; ++i) 
+                for (i = 0; i < SDL_X11_CLIPBOARD_MIME_TYPE_MAX; ++i)
                     supportedFormats[mime_formats++] = X11_GetSDLCutBufferClipboardExternalFormat(display, i);
                 X11_XChangeProperty(display, req->requestor, req->property,
                     XA_ATOM, 32, PropModeReplace,
@@ -657,7 +657,7 @@ X11_HandleClipboardEvent(_THIS, const XEvent *xevent)
                     if (X11_GetSDLCutBufferClipboardExternalFormat(display, i) != req->target)
                         continue;
                     if (X11_XGetWindowProperty(display, DefaultRootWindow(display),
-                        X11_GetSDLCutBufferClipboardType(display, i), 0, INT_MAX/4, False, X11_GetSDLCutBufferClipboardInternalFormat(display, i),
+                        X11_GetSDLCutBufferClipboardType(display, i, req->selection), 0, INT_MAX/4, False, X11_GetSDLCutBufferClipboardInternalFormat(display, i),
                         &sevent.xselection.target, &seln_format, &nbytes,
                         &overflow, &seln_data) == Success) {
                             if (seln_format != None) {
@@ -946,7 +946,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
             if (xevent->xcrossing.mode != NotifyGrab &&
                 xevent->xcrossing.mode != NotifyUngrab &&
                 xevent->xcrossing.detail != NotifyInferior) {
-                
+
                 /* In order for interaction with the window decorations and menu to work properly
                    on Mutter, we need to ungrab the keyboard when the the mouse leaves. */
                 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) {
@@ -1158,7 +1158,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
                                         &xevent->xconfigure.x, &xevent->xconfigure.y,
                                         &ChildReturn);
             }
-                
+
             if (xevent->xconfigure.x != data->last_xconfigure.x ||
                 xevent->xconfigure.y != data->last_xconfigure.y) {
                 SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MOVED,

+ 3 - 0
src/video/x11/SDL_x11video.c

@@ -301,6 +301,9 @@ X11_CreateDevice(void)
     device->SetClipboardText = X11_SetClipboardText;
     device->GetClipboardText = X11_GetClipboardText;
     device->HasClipboardText = X11_HasClipboardText;
+    device->SetPrimarySelectionText = X11_SetPrimarySelectionText;
+    device->GetPrimarySelectionText = X11_GetPrimarySelectionText;
+    device->HasPrimarySelectionText = X11_HasPrimarySelectionText;
     device->StartTextInput = X11_StartTextInput;
     device->StopTextInput = X11_StopTextInput;
     device->SetTextInputRect = X11_SetTextInputRect;

+ 158 - 5
test/testautomation_clipboard.c

@@ -27,6 +27,21 @@ clipboard_testHasClipboardText(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * \brief Check call to SDL_HasPrimarySelectionText
+ *
+ * \sa
+ * http://wiki.libsdl.org/SDL_HasPrimarySelectionText
+ */
+int
+clipboard_testHasPrimarySelectionText(void *arg)
+{
+    SDL_HasPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_HasPrimarySelectionText succeeded");
+
+    return TEST_COMPLETED;
+}
+
 /**
  * \brief Check call to SDL_GetClipboardText
  *
@@ -45,6 +60,24 @@ clipboard_testGetClipboardText(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * \brief Check call to SDL_GetPrimarySelectionText
+ *
+ * \sa
+ * http://wiki.libsdl.org/SDL_GetPrimarySelectionText
+ */
+int
+clipboard_testGetPrimarySelectionText(void *arg)
+{
+    char *charResult;
+    charResult = SDL_GetPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_GetPrimarySelectionText succeeded");
+
+    SDL_free(charResult);
+
+    return TEST_COMPLETED;
+}
+
 /**
  * \brief Check call to SDL_SetClipboardText
  * \sa
@@ -71,7 +104,36 @@ clipboard_testSetClipboardText(void *arg)
     SDL_free(textRef);
     SDL_free(text);
 
-   return TEST_COMPLETED;
+    return TEST_COMPLETED;
+}
+
+/**
+ * \brief Check call to SDL_SetPrimarySelectionText
+ * \sa
+ * http://wiki.libsdl.org/SDL_SetPrimarySelectionText
+ */
+int
+clipboard_testSetPrimarySelectionText(void *arg)
+{
+    char *textRef = SDLTest_RandomAsciiString();
+    char *text = SDL_strdup(textRef);
+    int result;
+    result = SDL_SetPrimarySelectionText((const char *)text);
+    SDLTest_AssertPass("Call to SDL_SetPrimarySelectionText succeeded");
+    SDLTest_AssertCheck(
+        result == 0,
+        "Validate SDL_SetPrimarySelectionText result, expected 0, got %i",
+        result);
+    SDLTest_AssertCheck(
+        SDL_strcmp(textRef, text) == 0,
+        "Verify SDL_SetPrimarySelectionText did not modify input string, expected '%s', got '%s'",
+        textRef, text);
+
+    /* Cleanup */
+    SDL_free(textRef);
+    SDL_free(text);
+
+    return TEST_COMPLETED;
 }
 
 /**
@@ -150,7 +212,86 @@ clipboard_testClipboardTextFunctions(void *arg)
     SDL_free(text);
     SDL_free(charResult);
 
-   return TEST_COMPLETED;
+    return TEST_COMPLETED;
+}
+
+/**
+ * \brief End-to-end test of SDL_xyzPrimarySelectionText functions
+ * \sa
+ * http://wiki.libsdl.org/SDL_HasPrimarySelectionText
+ * http://wiki.libsdl.org/SDL_GetPrimarySelectionText
+ * http://wiki.libsdl.org/SDL_SetPrimarySelectionText
+ */
+int
+clipboard_testPrimarySelectionTextFunctions(void *arg)
+{
+    char *textRef = SDLTest_RandomAsciiString();
+    char *text = SDL_strdup(textRef);
+    SDL_bool boolResult;
+    int intResult;
+    char *charResult;
+
+    /* Clear primary selection text state */
+    boolResult = SDL_HasPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_HasPrimarySelectionText succeeded");
+    if (boolResult == SDL_TRUE) {
+        intResult = SDL_SetPrimarySelectionText((const char *)NULL);
+        SDLTest_AssertPass("Call to SDL_SetPrimarySelectionText(NULL) succeeded");
+        SDLTest_AssertCheck(
+            intResult == 0,
+            "Verify result from SDL_SetPrimarySelectionText(NULL), expected 0, got %i",
+            intResult);
+        charResult = SDL_GetPrimarySelectionText();
+        SDLTest_AssertPass("Call to SDL_GetPrimarySelectionText succeeded");
+        SDL_free(charResult);
+        boolResult = SDL_HasPrimarySelectionText();
+        SDLTest_AssertPass("Call to SDL_HasPrimarySelectionText succeeded");
+        SDLTest_AssertCheck(
+            boolResult == SDL_FALSE,
+            "Verify SDL_HasPrimarySelectionText returned SDL_FALSE, got %s",
+            (boolResult) ? "SDL_TRUE" : "SDL_FALSE");
+    }
+
+    /* Empty primary selection  */
+    charResult = SDL_GetPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_GetPrimarySelectionText succeeded");
+    SDLTest_AssertCheck(
+        charResult != NULL,
+        "Verify SDL_GetPrimarySelectionText did not return NULL");
+    SDLTest_AssertCheck(
+        charResult[0] == '\0',
+        "Verify SDL_GetPrimarySelectionText returned string with length 0, got length %i",
+        (int) SDL_strlen(charResult));
+    intResult = SDL_SetPrimarySelectionText((const char *)text);
+    SDLTest_AssertPass("Call to SDL_SetPrimarySelectionText succeeded");
+    SDLTest_AssertCheck(
+        intResult == 0,
+        "Verify result from SDL_SetPrimarySelectionText(NULL), expected 0, got %i",
+        intResult);
+    SDLTest_AssertCheck(
+        SDL_strcmp(textRef, text) == 0,
+        "Verify SDL_SetPrimarySelectionText did not modify input string, expected '%s', got '%s'",
+        textRef, text);
+    boolResult = SDL_HasPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_HasPrimarySelectionText succeeded");
+    SDLTest_AssertCheck(
+        boolResult == SDL_TRUE,
+        "Verify SDL_HasPrimarySelectionText returned SDL_TRUE, got %s",
+        (boolResult) ? "SDL_TRUE" : "SDL_FALSE");
+    SDL_free(charResult);
+    charResult = SDL_GetPrimarySelectionText();
+    SDLTest_AssertPass("Call to SDL_GetPrimarySelectionText succeeded");
+    SDLTest_AssertCheck(
+        SDL_strcmp(textRef, charResult) == 0,
+        "Verify SDL_GetPrimarySelectionText returned correct string, expected '%s', got '%s'",
+        textRef, charResult);
+
+    /* Cleanup */
+    SDL_free(textRef);
+    SDL_free(text);
+    SDL_free(charResult);
+
+    return TEST_COMPLETED;
 }
 
 
@@ -161,17 +302,29 @@ static const SDLTest_TestCaseReference clipboardTest1 =
         { (SDLTest_TestCaseFp)clipboard_testHasClipboardText, "clipboard_testHasClipboardText", "Check call to SDL_HasClipboardText", TEST_ENABLED };
 
 static const SDLTest_TestCaseReference clipboardTest2 =
-        { (SDLTest_TestCaseFp)clipboard_testGetClipboardText, "clipboard_testGetClipboardText", "Check call to SDL_GetClipboardText", TEST_ENABLED };
+        { (SDLTest_TestCaseFp)clipboard_testHasPrimarySelectionText, "clipboard_testHasPrimarySelectionText", "Check call to SDL_HasPrimarySelectionText", TEST_ENABLED };
 
 static const SDLTest_TestCaseReference clipboardTest3 =
-        { (SDLTest_TestCaseFp)clipboard_testSetClipboardText, "clipboard_testSetClipboardText", "Check call to SDL_SetClipboardText", TEST_ENABLED };
+        { (SDLTest_TestCaseFp)clipboard_testGetClipboardText, "clipboard_testGetClipboardText", "Check call to SDL_GetClipboardText", TEST_ENABLED };
 
 static const SDLTest_TestCaseReference clipboardTest4 =
+        { (SDLTest_TestCaseFp)clipboard_testGetPrimarySelectionText, "clipboard_testGetPrimarySelectionText", "Check call to SDL_GetPrimarySelectionText", TEST_ENABLED };
+
+static const SDLTest_TestCaseReference clipboardTest5 =
+        { (SDLTest_TestCaseFp)clipboard_testSetClipboardText, "clipboard_testSetClipboardText", "Check call to SDL_SetClipboardText", TEST_ENABLED };
+
+static const SDLTest_TestCaseReference clipboardTest6 =
+        { (SDLTest_TestCaseFp)clipboard_testSetPrimarySelectionText, "clipboard_testSetPrimarySelectionText", "Check call to SDL_SetPrimarySelectionText", TEST_ENABLED };
+
+static const SDLTest_TestCaseReference clipboardTest7 =
         { (SDLTest_TestCaseFp)clipboard_testClipboardTextFunctions, "clipboard_testClipboardTextFunctions", "End-to-end test of SDL_xyzClipboardText functions", TEST_ENABLED };
 
+static const SDLTest_TestCaseReference clipboardTest8 =
+        { (SDLTest_TestCaseFp)clipboard_testPrimarySelectionTextFunctions, "clipboard_testPrimarySelectionTextFunctions", "End-to-end test of SDL_xyzPrimarySelectionText functions", TEST_ENABLED };
+
 /* Sequence of Clipboard test cases */
 static const SDLTest_TestCaseReference *clipboardTests[] =  {
-    &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, NULL
+    &clipboardTest1, &clipboardTest2, &clipboardTest3, &clipboardTest4, &clipboardTest5, &clipboardTest6, &clipboardTest7, &clipboardTest8, NULL
 };
 
 /* Clipboard test suite (global) */

+ 225 - 0
wayland-protocols/primary-selection-unstable-v1.xml

@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_primary_selection_unstable_v1">
+  <copyright>
+    Copyright © 2015, 2016 Red Hat
+
+    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>
+
+  <description summary="Primary selection protocol">
+    This protocol provides the ability to have a primary selection device to
+    match that of the X server. This primary selection is a shortcut to the
+    common clipboard selection, where text just needs to be selected in order
+    to allow copying it elsewhere. The de facto way to perform this action
+    is the middle mouse button, although it is not limited to this one.
+
+    Clients wishing to honor primary selection should create a primary
+    selection source and set it as the selection through
+    wp_primary_selection_device.set_selection whenever the text selection
+    changes. In order to minimize calls in pointer-driven text selection,
+    it should happen only once after the operation finished. Similarly,
+    a NULL source should be set when text is unselected.
+
+    wp_primary_selection_offer objects are first announced through the
+    wp_primary_selection_device.data_offer event. Immediately after this event,
+    the primary data offer will emit wp_primary_selection_offer.offer events
+    to let know of the mime types being offered.
+
+    When the primary selection changes, the client with the keyboard focus
+    will receive wp_primary_selection_device.selection events. Only the client
+    with the keyboard focus will receive such events with a non-NULL
+    wp_primary_selection_offer. Across keyboard focus changes, previously
+    focused clients will receive wp_primary_selection_device.events with a
+    NULL wp_primary_selection_offer.
+
+    In order to request the primary selection data, the client must pass
+    a recent serial pertaining to the press event that is triggering the
+    operation, if the compositor deems the serial valid and recent, the
+    wp_primary_selection_source.send event will happen in the other end
+    to let the transfer begin. The client owning the primary selection
+    should write the requested data, and close the file descriptor
+    immediately.
+
+    If the primary selection owner client disappeared during the transfer,
+    the client reading the data will receive a
+    wp_primary_selection_device.selection event with a NULL
+    wp_primary_selection_offer, the client should take this as a hint
+    to finish the reads related to the no longer existing offer.
+
+    The primary selection owner should be checking for errors during
+    writes, merely cancelling the ongoing transfer if any happened.
+  </description>
+
+  <interface name="zwp_primary_selection_device_manager_v1" version="1">
+    <description summary="X primary selection emulation">
+      The primary selection device manager is a singleton global object that
+      provides access to the primary selection. It allows to create
+      wp_primary_selection_source objects, as well as retrieving the per-seat
+      wp_primary_selection_device objects.
+    </description>
+
+    <request name="create_source">
+      <description summary="create a new primary selection source">
+        Create a new primary selection source.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_source_v1"/>
+    </request>
+
+    <request name="get_device">
+      <description summary="create a new primary selection device">
+        Create a new data device for a given seat.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_primary_selection_device_v1"/>
+      <arg name="seat" type="object" interface="wl_seat"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device manager">
+        Destroy the primary selection device manager.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_device_v1" version="1">
+    <request name="set_selection">
+      <description summary="set the primary selection">
+        Replaces the current selection. The previous owner of the primary
+        selection will receive a wp_primary_selection_source.cancelled event.
+
+        To unset the selection, set the source to NULL.
+      </description>
+      <arg name="source" type="object" interface="zwp_primary_selection_source_v1" allow-null="true"/>
+      <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+    </request>
+
+    <event name="data_offer">
+      <description summary="introduce a new wp_primary_selection_offer">
+        Introduces a new wp_primary_selection_offer object that may be used
+        to receive the current primary selection. Immediately following this
+        event, the new wp_primary_selection_offer object will send
+        wp_primary_selection_offer.offer events to describe the offered mime
+        types.
+      </description>
+      <arg name="offer" type="new_id" interface="zwp_primary_selection_offer_v1"/>
+    </event>
+
+    <event name="selection">
+      <description summary="advertise a new primary selection">
+        The wp_primary_selection_device.selection event is sent to notify the
+        client of a new primary selection. This event is sent after the
+        wp_primary_selection.data_offer event introducing this object, and after
+        the offer has announced its mimetypes through
+        wp_primary_selection_offer.offer.
+
+        The data_offer is valid until a new offer or NULL is received
+        or until the client loses keyboard focus. The client must destroy the
+        previous selection data_offer, if any, upon receiving this event.
+      </description>
+      <arg name="id" type="object" interface="zwp_primary_selection_offer_v1" allow-null="true"/>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection device">
+        Destroy the primary selection device.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwp_primary_selection_offer_v1" version="1">
+    <description summary="offer to transfer primary selection contents">
+      A wp_primary_selection_offer represents an offer to transfer the contents
+      of the primary selection clipboard to the client. Similar to
+      wl_data_offer, the offer also describes the mime types that the data can
+      be converted to and provides the mechanisms for transferring the data
+      directly to the client.
+    </description>
+
+    <request name="receive">
+      <description summary="request that the data is transferred">
+        To transfer the contents of the primary selection clipboard, the client
+        issues this request and indicates the mime type that it wants to
+        receive. The transfer happens through the passed file descriptor
+        (typically created with the pipe system call). The source client writes
+        the data in the mime type representation requested and then closes the
+        file descriptor.
+
+        The receiving client reads from the read end of the pipe until EOF and
+        closes its end, at which point the transfer is complete.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection offer">
+        Destroy the primary selection offer.
+      </description>
+    </request>
+
+    <event name="offer">
+      <description summary="advertise offered mime type">
+        Sent immediately after creating announcing the
+        wp_primary_selection_offer through
+        wp_primary_selection_device.data_offer. One event is sent per offered
+        mime type.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </event>
+  </interface>
+
+  <interface name="zwp_primary_selection_source_v1" version="1">
+    <description summary="offer to replace the contents of the primary selection">
+      The source side of a wp_primary_selection_offer, it provides a way to
+      describe the offered data and respond to requests to transfer the
+      requested contents of the primary selection clipboard.
+    </description>
+
+    <request name="offer">
+      <description summary="add an offered mime type">
+        This request adds a mime type to the set of mime types advertised to
+        targets. Can be called several times to offer multiple types.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the primary selection source">
+        Destroy the primary selection source.
+      </description>
+    </request>
+
+    <event name="send">
+      <description summary="send the primary selection contents">
+        Request for the current primary selection contents from the client.
+        Send the specified mime type over the passed file descriptor, then
+        close it.
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </event>
+
+    <event name="cancelled">
+      <description summary="request for primary selection contents was canceled">
+        This primary selection source is no longer valid. The client should
+        clean up and destroy this primary selection source.
+      </description>
+    </event>
+  </interface>
+</protocol>