|
@@ -26,6 +26,42 @@
|
|
|
|
|
|
#include "SDL_x11video.h"
|
|
|
#include "SDL_x11clipboard.h"
|
|
|
+#include "../../events/SDL_events_c.h"
|
|
|
+
|
|
|
+#define TEXT_MIME_TYPES_LEN 5
|
|
|
+static const char *text_mime_types[TEXT_MIME_TYPES_LEN] = {
|
|
|
+ "text/plain;charset=utf-8",
|
|
|
+ "text/plain",
|
|
|
+ "TEXT",
|
|
|
+ "UTF8_STRING",
|
|
|
+ "STRING",
|
|
|
+};
|
|
|
+
|
|
|
+static void *X11_ClipboardTextCallback(size_t *length, const char *mime_type, void *userdata)
|
|
|
+{
|
|
|
+ void *data = NULL;
|
|
|
+ SDL_bool valid_mime_type = SDL_FALSE;
|
|
|
+ *length = 0;
|
|
|
+
|
|
|
+ if (userdata == NULL) {
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (size_t i = 0; i < TEXT_MIME_TYPES_LEN; ++i) {
|
|
|
+ if (SDL_strcmp(mime_type, text_mime_types[i]) == 0) {
|
|
|
+ valid_mime_type = SDL_TRUE;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (valid_mime_type) {
|
|
|
+ char *text = userdata;
|
|
|
+ *length = SDL_strlen(text);
|
|
|
+ data = userdata;
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+}
|
|
|
|
|
|
/* Get any application owned window handle for clipboard association */
|
|
|
static Window GetWindow(SDL_VideoDevice *_this)
|
|
@@ -49,130 +85,113 @@ static Window GetWindow(SDL_VideoDevice *_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. */
|
|
|
-Atom X11_GetSDLCutBufferClipboardType(Display *display, enum ESDLX11ClipboardMimeType mime_type,
|
|
|
- Atom selection_type)
|
|
|
+static int SetSelectionData(SDL_VideoDevice *_this, Atom selection, SDL_ClipboardDataCallback callback,
|
|
|
+ size_t mime_count, const char **mime_types, void *userdata, SDL_bool internal)
|
|
|
{
|
|
|
- switch (mime_type) {
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_STRING:
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN:
|
|
|
-#ifdef X_HAVE_UTF8_STRING
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
|
-#endif
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT:
|
|
|
- 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;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-Atom X11_GetSDLCutBufferClipboardExternalFormat(Display *display, enum ESDLX11ClipboardMimeType mime_type)
|
|
|
-{
|
|
|
- switch (mime_type) {
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_STRING:
|
|
|
-/* If you don't support UTF-8, you might use XA_STRING here */
|
|
|
-#ifdef X_HAVE_UTF8_STRING
|
|
|
- return X11_XInternAtom(display, "UTF8_STRING", False);
|
|
|
-#else
|
|
|
- return XA_STRING;
|
|
|
-#endif
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN:
|
|
|
- return X11_XInternAtom(display, "text/plain", False);
|
|
|
-#ifdef X_HAVE_UTF8_STRING
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
|
- return X11_XInternAtom(display, "text/plain;charset=utf-8", False);
|
|
|
-#endif
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT:
|
|
|
- return X11_XInternAtom(display, "TEXT", False);
|
|
|
- default:
|
|
|
- SDL_SetError("Can't find mime_type.");
|
|
|
- return XA_STRING;
|
|
|
- }
|
|
|
-}
|
|
|
-Atom X11_GetSDLCutBufferClipboardInternalFormat(Display *display, enum ESDLX11ClipboardMimeType mime_type)
|
|
|
-{
|
|
|
- switch (mime_type) {
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_STRING:
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN:
|
|
|
-#ifdef X_HAVE_UTF8_STRING
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
|
-#endif
|
|
|
- case SDL_X11_CLIPBOARD_MIME_TYPE_TEXT:
|
|
|
-/* If you don't support UTF-8, you might use XA_STRING here */
|
|
|
-#ifdef X_HAVE_UTF8_STRING
|
|
|
- return X11_XInternAtom(display, "UTF8_STRING", False);
|
|
|
-#else
|
|
|
- return XA_STRING;
|
|
|
-#endif
|
|
|
- default:
|
|
|
- SDL_SetError("Can't find mime_type.");
|
|
|
- return XA_STRING;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-static int SetSelectionText(SDL_VideoDevice *_this, const char *text, Atom selection_type)
|
|
|
-{
|
|
|
- Display *display = _this->driverdata->display;
|
|
|
+ SDL_VideoData *videodata = _this->driverdata;
|
|
|
+ Display *display = videodata->display;
|
|
|
Window window;
|
|
|
+ SDLX11_ClipboardData *clipboard;
|
|
|
+ SDL_bool clipboard_owner = SDL_FALSE;
|
|
|
|
|
|
- /* Get the SDL window that will own the selection */
|
|
|
window = GetWindow(_this);
|
|
|
if (window == None) {
|
|
|
return SDL_SetError("Couldn't find a window to own the selection");
|
|
|
}
|
|
|
|
|
|
- /* Save the selection on the root window */
|
|
|
- X11_XChangeProperty(display, DefaultRootWindow(display),
|
|
|
- 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 (selection == XA_PRIMARY) {
|
|
|
+ clipboard = &videodata->primary_selection;
|
|
|
+ } else {
|
|
|
+ clipboard = &videodata->clipboard;
|
|
|
+ }
|
|
|
|
|
|
- if (X11_XGetSelectionOwner(display, selection_type) != window) {
|
|
|
- X11_XSetSelectionOwner(display, selection_type, window, CurrentTime);
|
|
|
+ clipboard_owner = X11_XGetSelectionOwner(display, selection) == window;
|
|
|
+
|
|
|
+ /* If we are cancelling our own data we need to clean it up */
|
|
|
+ if (clipboard_owner) {
|
|
|
+ if (clipboard->internal == SDL_TRUE) {
|
|
|
+ SDL_free(clipboard->userdata);
|
|
|
+ } else {
|
|
|
+ SDL_SendClipboardCancelled(clipboard->userdata);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ clipboard->callback = callback;
|
|
|
+ clipboard->userdata = userdata;
|
|
|
+ clipboard->mime_types = mime_types;
|
|
|
+ clipboard->mime_count = mime_count;
|
|
|
+ clipboard->internal = internal;
|
|
|
+
|
|
|
+ if (!clipboard_owner) {
|
|
|
+ X11_XSetSelectionOwner(display, selection, window, CurrentTime);
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static char *GetSelectionText(SDL_VideoDevice *_this, Atom selection_type)
|
|
|
+static void *CloneDataBuffer(void *buffer, size_t *len, SDL_bool nullterminate)
|
|
|
+{
|
|
|
+ void *clone = NULL;
|
|
|
+ if (*len > 0 && buffer != NULL) {
|
|
|
+ if (nullterminate == SDL_TRUE) {
|
|
|
+ clone = SDL_malloc((*len)+1);
|
|
|
+ if (clone == NULL) {
|
|
|
+ SDL_OutOfMemory();
|
|
|
+ } else {
|
|
|
+ SDL_memcpy(clone, buffer, *len);
|
|
|
+ ((char *) clone)[*len] = '\0';
|
|
|
+ *len += 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ clone = SDL_malloc(*len);
|
|
|
+ if (clone == NULL) {
|
|
|
+ SDL_OutOfMemory();
|
|
|
+ } else {
|
|
|
+ SDL_memcpy(clone, buffer, *len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return clone;
|
|
|
+}
|
|
|
+
|
|
|
+static void *GetSelectionData(SDL_VideoDevice *_this, Atom selection_type, size_t *length,
|
|
|
+ const char *mime_type, SDL_bool nullterminate)
|
|
|
{
|
|
|
SDL_VideoData *videodata = _this->driverdata;
|
|
|
Display *display = videodata->display;
|
|
|
- Atom format;
|
|
|
Window window;
|
|
|
Window owner;
|
|
|
Atom selection;
|
|
|
Atom seln_type;
|
|
|
int seln_format;
|
|
|
- unsigned long nbytes;
|
|
|
unsigned long overflow;
|
|
|
- unsigned char *src;
|
|
|
- char *text;
|
|
|
Uint64 waitStart;
|
|
|
Uint64 waitElapsed;
|
|
|
|
|
|
- text = NULL;
|
|
|
+ void *data = NULL;
|
|
|
+ unsigned char *src = NULL;
|
|
|
+ Atom XA_MIME = X11_XInternAtom(display, mime_type, False);
|
|
|
+ *length = 0;
|
|
|
|
|
|
/* Get the window that holds the selection */
|
|
|
window = GetWindow(_this);
|
|
|
- format = X11_GetSDLCutBufferClipboardInternalFormat(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING);
|
|
|
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);
|
|
|
- selection = XA_CUT_BUFFER0;
|
|
|
- format = XA_STRING;
|
|
|
+ /* This requires a fallback to ancient X10 cut-buffers. We will just skip those for now */
|
|
|
+ return NULL;
|
|
|
} else if (owner == window) {
|
|
|
owner = DefaultRootWindow(display);
|
|
|
- selection = X11_GetSDLCutBufferClipboardType(display, SDL_X11_CLIPBOARD_MIME_TYPE_STRING, selection_type);
|
|
|
+ if (selection_type == XA_PRIMARY) {
|
|
|
+ src = videodata->primary_selection.callback(length, mime_type, videodata->primary_selection.userdata);
|
|
|
+ } else {
|
|
|
+ src = videodata->clipboard.callback(length, mime_type, videodata->clipboard.userdata);
|
|
|
+ }
|
|
|
+
|
|
|
+ data = CloneDataBuffer(src, length, nullterminate);
|
|
|
} else {
|
|
|
/* Request that the selection owner copy the data to our window */
|
|
|
owner = window;
|
|
|
selection = X11_XInternAtom(display, "SDL_SELECTION", False);
|
|
|
- X11_XConvertSelection(display, selection_type, format, selection, owner,
|
|
|
+ X11_XConvertSelection(display, selection_type, XA_MIME, selection, owner,
|
|
|
CurrentTime);
|
|
|
|
|
|
/* When using synergy on Linux and when data has been put in the clipboard
|
|
@@ -189,29 +208,63 @@ static char *GetSelectionText(SDL_VideoDevice *_this, Atom selection_type)
|
|
|
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. */
|
|
|
- SetSelectionText(_this, "", selection_type);
|
|
|
- return SDL_strdup("");
|
|
|
+ SetSelectionData(_this, selection_type, X11_ClipboardTextCallback, TEXT_MIME_TYPES_LEN,
|
|
|
+ text_mime_types, NULL, SDL_TRUE);
|
|
|
+ data = NULL;
|
|
|
+ *length = 0;
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False,
|
|
|
- format, &seln_type, &seln_format, &nbytes, &overflow, &src) == Success) {
|
|
|
- if (seln_type == format) {
|
|
|
- text = (char *)SDL_malloc(nbytes + 1);
|
|
|
- if (text) {
|
|
|
- SDL_memcpy(text, src, nbytes);
|
|
|
- text[nbytes] = '\0';
|
|
|
+ if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False,
|
|
|
+ XA_MIME, &seln_type, &seln_format, length, &overflow, &src) == Success) {
|
|
|
+ if (seln_type == XA_MIME) {
|
|
|
+ data = CloneDataBuffer(src, length, nullterminate);
|
|
|
}
|
|
|
+ X11_XFree(src);
|
|
|
}
|
|
|
- X11_XFree(src);
|
|
|
}
|
|
|
|
|
|
- if (text == NULL) {
|
|
|
- text = SDL_strdup("");
|
|
|
+ return data;
|
|
|
+}
|
|
|
+
|
|
|
+int X11_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count,
|
|
|
+ const char **mime_types, void *userdata)
|
|
|
+{
|
|
|
+ SDL_VideoData *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 SetSelectionData(_this, XA_CLIPBOARD, callback, mime_count, mime_types, userdata, SDL_FALSE);
|
|
|
+}
|
|
|
|
|
|
- return text;
|
|
|
+void *X11_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type)
|
|
|
+{
|
|
|
+ SDL_VideoData *videodata = _this->driverdata;
|
|
|
+ Atom XA_CLIPBOARD = X11_XInternAtom(videodata->display, "CLIPBOARD", 0);
|
|
|
+ if (XA_CLIPBOARD == None) {
|
|
|
+ SDL_SetError("Couldn't access X clipboard");
|
|
|
+ *length = 0;
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return GetSelectionData(_this, XA_CLIPBOARD, length, mime_type, SDL_FALSE);
|
|
|
+}
|
|
|
+
|
|
|
+SDL_bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
|
|
|
+{
|
|
|
+ size_t length;
|
|
|
+ void *data;
|
|
|
+ data = X11_GetClipboardData(_this, &length, mime_type);
|
|
|
+ if (data != NULL && length > 0) {
|
|
|
+ SDL_free(data);
|
|
|
+ }
|
|
|
+ return length > 0;
|
|
|
+}
|
|
|
+
|
|
|
+void *X11_GetClipboardUserdata(SDL_VideoDevice *_this)
|
|
|
+{
|
|
|
+ SDLX11_ClipboardData *cb = &_this->driverdata->clipboard;
|
|
|
+ return cb->internal ? NULL : cb->userdata;
|
|
|
}
|
|
|
|
|
|
int X11_SetClipboardText(SDL_VideoDevice *_this, const char *text)
|
|
@@ -221,30 +274,35 @@ int X11_SetClipboardText(SDL_VideoDevice *_this, const char *text)
|
|
|
if (XA_CLIPBOARD == None) {
|
|
|
return SDL_SetError("Couldn't access X clipboard");
|
|
|
}
|
|
|
- return SetSelectionText(_this, text, XA_CLIPBOARD);
|
|
|
+ return SetSelectionData(_this, XA_CLIPBOARD, X11_ClipboardTextCallback, TEXT_MIME_TYPES_LEN, text_mime_types,
|
|
|
+ SDL_strdup(text), SDL_TRUE);
|
|
|
}
|
|
|
|
|
|
int X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text)
|
|
|
{
|
|
|
- return SetSelectionText(_this, text, XA_PRIMARY);
|
|
|
+ return SetSelectionData(_this, XA_PRIMARY, X11_ClipboardTextCallback, TEXT_MIME_TYPES_LEN, text_mime_types,
|
|
|
+ SDL_strdup(text), SDL_TRUE);
|
|
|
}
|
|
|
|
|
|
char *
|
|
|
X11_GetClipboardText(SDL_VideoDevice *_this)
|
|
|
{
|
|
|
+ size_t length;
|
|
|
SDL_VideoData *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 GetSelectionText(_this, XA_CLIPBOARD);
|
|
|
+
|
|
|
+ return GetSelectionData(_this, XA_CLIPBOARD, &length, text_mime_types[0], SDL_TRUE);
|
|
|
}
|
|
|
|
|
|
char *
|
|
|
X11_GetPrimarySelectionText(SDL_VideoDevice *_this)
|
|
|
{
|
|
|
- return GetSelectionText(_this, XA_PRIMARY);
|
|
|
+ size_t length;
|
|
|
+ return GetSelectionData(_this, XA_PRIMARY, &length, text_mime_types[0], SDL_TRUE);
|
|
|
}
|
|
|
|
|
|
SDL_bool
|
|
@@ -271,4 +329,16 @@ X11_HasPrimarySelectionText(SDL_VideoDevice *_this)
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+void
|
|
|
+X11_QuitClipboard(SDL_VideoDevice *_this)
|
|
|
+{
|
|
|
+ SDL_VideoData *data = _this->driverdata;
|
|
|
+ if (data->primary_selection.internal == SDL_TRUE) {
|
|
|
+ SDL_free(data->primary_selection.userdata);
|
|
|
+ }
|
|
|
+ if (data->clipboard.internal == SDL_TRUE) {
|
|
|
+ SDL_free(data->clipboard.userdata);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#endif /* SDL_VIDEO_DRIVER_X11 */
|