x11: Make a separate unmapped window to own clipboard selections.

Now the clipboard isn't lost if you destroy a specific SDL_Window, as it
works on other platforms. You will still lose the clipboard data on
SDL_Quit() or process termination, but that's X11 for you; run a
Clipboard Manager daemon.

Fixes Bugzilla #3222.
Fixes Bugzilla #3718.
Ryan C. Gordon 2017-07-31 13:49:22 -04:00
parent 997c69b9ef
commit 2ffd6d0208
4 changed files with 116 additions and 67 deletions

View File

@ -40,13 +40,23 @@
static Window
GetWindow(_THIS)
{
SDL_Window *window;
SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
window = _this->windows;
if (window) {
return ((SDL_WindowData *) window->driverdata)->xwindow;
/* We create an unmapped window that exists just to manage the clipboard,
since X11 selection data is tied to a specific window and dies with it.
We create the window on demand, so apps that don't use the clipboard
don't have to keep an unnecessary resource around. */
if (data->clipboard_window == None) {
Display *dpy = data->display;
Window parent = RootWindow(dpy, DefaultScreen(dpy));
XSetWindowAttributes xattr;
data->clipboard_window = X11_XCreateWindow(dpy, parent, -10, -10, 1, 1, 0,
CopyFromParent, InputOnly,
CopyFromParent, 0, &xattr);
X11_XFlush(data->display);
}
return None;
return data->clipboard_window;
}
/* We use our own cut-buffer for intermediate storage instead of

View File

@ -38,6 +38,7 @@
#include "SDL_hints.h"
#include "SDL_timer.h"
#include "SDL_syswm.h"
#include "SDL_assert.h"
#include <stdio.h>
@ -537,6 +538,95 @@ X11_UpdateUserTime(SDL_WindowData *data, const unsigned long latest)
}
}
static void
X11_HandleClipboardEvent(_THIS, const XEvent *xevent)
{
SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
Display *display = videodata->display;
SDL_assert(videodata->clipboard_window != None);
SDL_assert(xevent->xany.window == videodata->clipboard_window);
switch (xevent->type) {
/* Copy the selection from our own CUTBUFFER to the requested property */
case SelectionRequest: {
const XSelectionRequestEvent *req = &xevent->xselectionrequest;
XEvent sevent;
int seln_format;
unsigned long nbytes;
unsigned long overflow;
unsigned char *seln_data;
#ifdef DEBUG_XEVENTS
printf("window CLIPBOARD: SelectionRequest (requestor = %ld, target = %ld)\n",
req->requestor, req->target);
#endif
SDL_zero(sevent);
sevent.xany.type = SelectionNotify;
sevent.xselection.selection = req->selection;
sevent.xselection.target = None;
sevent.xselection.property = None; /* tell them no by default */
sevent.xselection.requestor = req->requestor;
sevent.xselection.time = req->time;
/* !!! 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 (X11_XGetWindowProperty(display, DefaultRootWindow(display),
X11_GetSDLCutBufferClipboardType(display), 0, INT_MAX/4, False, req->target,
&sevent.xselection.target, &seln_format, &nbytes,
&overflow, &seln_data) == Success) {
/* !!! FIXME: cache atoms */
Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
if (sevent.xselection.target == req->target) {
X11_XChangeProperty(display, req->requestor, req->property,
sevent.xselection.target, seln_format, PropModeReplace,
seln_data, nbytes);
sevent.xselection.property = req->property;
} else if (XA_TARGETS == req->target) {
Atom SupportedFormats[] = { XA_TARGETS, sevent.xselection.target };
X11_XChangeProperty(display, req->requestor, req->property,
XA_ATOM, 32, PropModeReplace,
(unsigned char*)SupportedFormats,
SDL_arraysize(SupportedFormats));
sevent.xselection.property = req->property;
sevent.xselection.target = XA_TARGETS;
}
X11_XFree(seln_data);
}
X11_XSendEvent(display, req->requestor, False, 0, &sevent);
X11_XSync(display, False);
}
break;
case SelectionNotify: {
#ifdef DEBUG_XEVENTS
printf("window CLIPBOARD: SelectionNotify (requestor = %ld, target = %ld)\n",
xevent.xselection.requestor, xevent.xselection.target);
#endif
videodata->selection_waiting = SDL_FALSE;
}
break;
case SelectionClear: {
/* !!! FIXME: cache atoms */
Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
#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();
}
}
break;
}
}
static void
X11_DispatchEvent(_THIS)
@ -615,6 +705,12 @@ X11_DispatchEvent(_THIS)
xevent.type, xevent.xany.display, xevent.xany.window);
#endif
if ((videodata->clipboard_window != None) &&
(videodata->clipboard_window == xevent.xany.window)) {
X11_HandleClipboardEvent(_this, &xevent);
return;
}
data = NULL;
if (videodata && videodata->windowlist) {
for (i = 0; i < videodata->numwindows; ++i) {
@ -1223,55 +1319,6 @@ X11_DispatchEvent(_THIS)
}
break;
/* Copy the selection from our own CUTBUFFER to the requested property */
case SelectionRequest: {
XSelectionRequestEvent *req;
XEvent sevent;
int seln_format;
unsigned long nbytes;
unsigned long overflow;
unsigned char *seln_data;
req = &xevent.xselectionrequest;
#ifdef DEBUG_XEVENTS
printf("window %p: SelectionRequest (requestor = %ld, target = %ld)\n", data,
req->requestor, req->target);
#endif
SDL_zero(sevent);
sevent.xany.type = SelectionNotify;
sevent.xselection.selection = req->selection;
sevent.xselection.target = None;
sevent.xselection.property = None;
sevent.xselection.requestor = req->requestor;
sevent.xselection.time = req->time;
if (X11_XGetWindowProperty(display, DefaultRootWindow(display),
X11_GetSDLCutBufferClipboardType(display), 0, INT_MAX/4, False, req->target,
&sevent.xselection.target, &seln_format, &nbytes,
&overflow, &seln_data) == Success) {
Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
if (sevent.xselection.target == req->target) {
X11_XChangeProperty(display, req->requestor, req->property,
sevent.xselection.target, seln_format, PropModeReplace,
seln_data, nbytes);
sevent.xselection.property = req->property;
} else if (XA_TARGETS == req->target) {
Atom SupportedFormats[] = { XA_TARGETS, sevent.xselection.target };
X11_XChangeProperty(display, req->requestor, req->property,
XA_ATOM, 32, PropModeReplace,
(unsigned char*)SupportedFormats,
SDL_arraysize(SupportedFormats));
sevent.xselection.property = req->property;
sevent.xselection.target = XA_TARGETS;
}
X11_XFree(seln_data);
}
X11_XSendEvent(display, req->requestor, False, 0, &sevent);
X11_XSync(display, False);
}
break;
case SelectionNotify: {
Atom target = xevent.xselection.target;
#ifdef DEBUG_XEVENTS
@ -1315,19 +1362,6 @@ X11_DispatchEvent(_THIS)
X11_XSendEvent(display, data->xdnd_source, False, NoEventMask, (XEvent*)&m);
X11_XSync(display, False);
} else {
videodata->selection_waiting = SDL_FALSE;
}
}
break;
case SelectionClear: {
Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
if (xevent.xselectionclear.selection == XA_PRIMARY ||
(XA_CLIPBOARD != None && xevent.xselectionclear.selection == XA_CLIPBOARD)) {
SDL_SendClipboardUpdate();
}
}
break;

View File

@ -449,6 +449,10 @@ X11_VideoQuit(_THIS)
{
SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
if (data->clipboard_window) {
X11_XDestroyWindow(data->display, data->clipboard_window);
}
SDL_free(data->classname);
#ifdef X_HAVE_UTF8_STRING
if (data->im) {

View File

@ -82,6 +82,7 @@ typedef struct SDL_VideoData
SDL_WindowData **windowlist;
int windowlistlength;
XID window_group;
Window clipboard_window;
/* This is true for ICCCM2.0-compliant window managers */
SDL_bool net_wm;