diff --git a/src/video/x11/SDL_x11clipboard.c b/src/video/x11/SDL_x11clipboard.c index 89549fe2a..5f5a7caa9 100644 --- a/src/video/x11/SDL_x11clipboard.c +++ b/src/video/x11/SDL_x11clipboard.c @@ -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 (X11_XGetSelectionOwner(display, selection_type) != window) { - X11_XSetSelectionOwner(display, selection_type, window, CurrentTime); + if (selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else { + clipboard = &videodata->clipboard; } + 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, + XA_MIME, &seln_type, &seln_format, length, &overflow, &src) == Success) { + if (seln_type == XA_MIME) { + data = CloneDataBuffer(src, length, nullterminate); + } + X11_XFree(src); + } } - 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'; - } - } - X11_XFree(src); - } + return data; +} - if (text == NULL) { - text = SDL_strdup(""); +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 */ diff --git a/src/video/x11/SDL_x11clipboard.h b/src/video/x11/SDL_x11clipboard.h index 24116ae64..1143bc8c3 100644 --- a/src/video/x11/SDL_x11clipboard.h +++ b/src/video/x11/SDL_x11clipboard.h @@ -23,25 +23,27 @@ #ifndef SDL_x11clipboard_h_ #define SDL_x11clipboard_h_ -enum ESDLX11ClipboardMimeType -{ - SDL_X11_CLIPBOARD_MIME_TYPE_STRING, - SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN, -#ifdef X_HAVE_UTF8_STRING - SDL_X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8, -#endif - SDL_X11_CLIPBOARD_MIME_TYPE_TEXT, - SDL_X11_CLIPBOARD_MIME_TYPE_MAX -}; +#include +typedef struct X11_ClipboardData { + SDL_ClipboardDataCallback callback; + void *userdata; + const char **mime_types; + size_t mime_count; + SDL_bool internal; +} SDLX11_ClipboardData; + +extern int X11_SetClipboardData(SDL_VideoDevice *_this, SDL_ClipboardDataCallback callback, size_t mime_count, + const char **mime_types, void *userdata); +extern void *X11_GetClipboardData(SDL_VideoDevice *_this, size_t *length, const char *mime_type); +extern SDL_bool X11_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type); +extern void *X11_GetClipboardUserdata(SDL_VideoDevice *_this); extern int X11_SetClipboardText(SDL_VideoDevice *_this, const char *text); extern char *X11_GetClipboardText(SDL_VideoDevice *_this); extern SDL_bool X11_HasClipboardText(SDL_VideoDevice *_this); extern int X11_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text); extern char *X11_GetPrimarySelectionText(SDL_VideoDevice *_this); extern SDL_bool X11_HasPrimarySelectionText(SDL_VideoDevice *_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); +extern void X11_QuitClipboard(SDL_VideoDevice *_this); #endif /* SDL_x11clipboard_h_ */ diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 8c2304971..df058f67d 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -627,18 +627,28 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven { const XSelectionRequestEvent *req = &xevent->xselectionrequest; XEvent sevent; - int seln_format, mime_formats; + int mime_formats; unsigned long nbytes; - unsigned long overflow; unsigned char *seln_data; - Atom supportedFormats[SDL_X11_CLIPBOARD_MIME_TYPE_MAX + 1]; Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0); + SDLX11_ClipboardData *clipboard; #ifdef DEBUG_XEVENTS - printf("window CLIPBOARD: SelectionRequest (requestor = %ld, target = %ld)\n", - req->requestor, req->target); + char *atom_name; + atom_name = X11_XGetAtomName(display, req->target); + printf("window CLIPBOARD: SelectionRequest (requestor = %ld, target = %ld, mime_type = %s)\n", + req->requestor, req->target, atom_name); + if (atom_name) { + X11_XFree(atom_name); + } #endif + if (req->selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else { + clipboard = &videodata->clipboard; + } + SDL_zero(sevent); sevent.xany.type = SelectionNotify; sevent.xselection.selection = req->selection; @@ -652,10 +662,12 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven this now (or ever, really). */ if (req->target == XA_TARGETS) { + Atom *supportedFormats; + supportedFormats = SDL_malloc((clipboard->mime_count + 1) * sizeof(Atom)); supportedFormats[0] = XA_TARGETS; mime_formats = 1; - for (i = 0; i < SDL_X11_CLIPBOARD_MIME_TYPE_MAX; ++i) { - supportedFormats[mime_formats++] = X11_GetSDLCutBufferClipboardExternalFormat(display, i); + for (i = 0; i < clipboard->mime_count; ++i) { + supportedFormats[mime_formats++] = X11_XInternAtom(display, clipboard->mime_types[i], False); } X11_XChangeProperty(display, req->requestor, req->property, XA_ATOM, 32, PropModeReplace, @@ -663,25 +675,25 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven mime_formats); sevent.xselection.property = req->property; sevent.xselection.target = XA_TARGETS; + SDL_free(supportedFormats); } else { - for (i = 0; i < SDL_X11_CLIPBOARD_MIME_TYPE_MAX; ++i) { - if (X11_GetSDLCutBufferClipboardExternalFormat(display, i) != req->target) { - continue; - } - if (X11_XGetWindowProperty(display, DefaultRootWindow(display), - 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) { + if (clipboard->callback) { + for (i = 0; i < clipboard->mime_count; ++i) { + const char *mime_type = clipboard->mime_types[i]; + if (X11_XInternAtom(display, mime_type, False) != req->target) { + continue; + } + + /* FIXME: We don't support the X11 INCR protocol for large clipboards. Do we want that? */ + seln_data = clipboard->callback(&nbytes, mime_type, clipboard->userdata); + if (seln_data != NULL) { X11_XChangeProperty(display, req->requestor, req->property, - sevent.xselection.target, seln_format, PropModeReplace, + req->target, 8, PropModeReplace, seln_data, nbytes); sevent.xselection.property = req->property; - X11_XFree(seln_data); - break; - } else { - X11_XFree(seln_data); + sevent.xselection.target = req->target; } + break; } } } @@ -702,15 +714,24 @@ static void X11_HandleClipboardEvent(SDL_VideoDevice *_this, const XEvent *xeven { /* !!! FIXME: cache atoms */ Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0); + SDLX11_ClipboardData *clipboard = NULL; #ifdef DEBUG_XEVENTS printf("window CLIPBOARD: SelectionClear (requestor = %ld, target = %ld)\n", xevent->xselection.requestor, xevent->xselection.target); #endif - if (xevent->xselectionclear.selection == XA_PRIMARY || - (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD)) { - SDL_SendClipboardUpdate(); + if (xevent->xselectionclear.selection == XA_PRIMARY) { + clipboard = &videodata->primary_selection; + } else if (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD) { + clipboard = &videodata->clipboard; + if (clipboard->internal == SDL_FALSE) { + SDL_SendClipboardCancelled(clipboard->userdata); + } + } + if (clipboard != NULL && clipboard->internal == SDL_TRUE) { + SDL_free(clipboard->userdata); + clipboard->userdata = NULL; } } break; } diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 7d26a685c..6e8ef29fd 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -293,9 +293,13 @@ static SDL_VideoDevice *X11_CreateDevice(void) #endif #endif + device->SetClipboardData = X11_SetClipboardData; + device->GetClipboardData = X11_GetClipboardData; + device->HasClipboardData = X11_HasClipboardData; device->SetClipboardText = X11_SetClipboardText; device->GetClipboardText = X11_GetClipboardText; device->HasClipboardText = X11_HasClipboardText; + device->GetClipboardUserdata = X11_GetClipboardUserdata; device->SetPrimarySelectionText = X11_SetPrimarySelectionText; device->GetPrimarySelectionText = X11_GetPrimarySelectionText; device->HasPrimarySelectionText = X11_HasPrimarySelectionText; @@ -498,6 +502,7 @@ void X11_VideoQuit(SDL_VideoDevice *_this) X11_QuitKeyboard(_this); X11_QuitMouse(_this); X11_QuitTouch(_this); + X11_QuitClipboard(_this); } SDL_bool diff --git a/src/video/x11/SDL_x11video.h b/src/video/x11/SDL_x11video.h index aca07e14b..0b5df50c7 100644 --- a/src/video/x11/SDL_x11video.h +++ b/src/video/x11/SDL_x11video.h @@ -77,6 +77,8 @@ struct SDL_VideoData int windowlistlength; XID window_group; Window clipboard_window; + SDLX11_ClipboardData clipboard; + SDLX11_ClipboardData primary_selection; #ifdef SDL_VIDEO_DRIVER_X11_XFIXES SDL_Window *active_cursor_confined_window; #endif /* SDL_VIDEO_DRIVER_X11_XFIXES */