x11: Make a separate unmapped window to own clipboard selections.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 31 Jul 2017 13:49:22 -0400
changeset 111743767cdd2d64b
parent 11173 6709dc9adb16
child 11175 cbc6a8a5b701
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.
src/video/x11/SDL_x11clipboard.c
src/video/x11/SDL_x11events.c
src/video/x11/SDL_x11video.c
src/video/x11/SDL_x11video.h
     1.1 --- a/src/video/x11/SDL_x11clipboard.c	Tue Aug 01 20:16:10 2017 -0700
     1.2 +++ b/src/video/x11/SDL_x11clipboard.c	Mon Jul 31 13:49:22 2017 -0400
     1.3 @@ -40,13 +40,23 @@
     1.4  static Window
     1.5  GetWindow(_THIS)
     1.6  {
     1.7 -    SDL_Window *window;
     1.8 +    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
     1.9  
    1.10 -    window = _this->windows;
    1.11 -    if (window) {
    1.12 -        return ((SDL_WindowData *) window->driverdata)->xwindow;
    1.13 +    /* We create an unmapped window that exists just to manage the clipboard,
    1.14 +       since X11 selection data is tied to a specific window and dies with it.
    1.15 +       We create the window on demand, so apps that don't use the clipboard
    1.16 +       don't have to keep an unnecessary resource around. */
    1.17 +    if (data->clipboard_window == None) {
    1.18 +        Display *dpy = data->display;
    1.19 +        Window parent = RootWindow(dpy, DefaultScreen(dpy));
    1.20 +        XSetWindowAttributes xattr;
    1.21 +        data->clipboard_window = X11_XCreateWindow(dpy, parent, -10, -10, 1, 1, 0,
    1.22 +                                                   CopyFromParent, InputOnly,
    1.23 +                                                   CopyFromParent, 0, &xattr);
    1.24 +        X11_XFlush(data->display);
    1.25      }
    1.26 -    return None;
    1.27 +
    1.28 +    return data->clipboard_window;
    1.29  }
    1.30  
    1.31  /* We use our own cut-buffer for intermediate storage instead of  
     2.1 --- a/src/video/x11/SDL_x11events.c	Tue Aug 01 20:16:10 2017 -0700
     2.2 +++ b/src/video/x11/SDL_x11events.c	Mon Jul 31 13:49:22 2017 -0400
     2.3 @@ -38,6 +38,7 @@
     2.4  #include "SDL_hints.h"
     2.5  #include "SDL_timer.h"
     2.6  #include "SDL_syswm.h"
     2.7 +#include "SDL_assert.h"
     2.8  
     2.9  #include <stdio.h>
    2.10  
    2.11 @@ -537,6 +538,95 @@
    2.12      }
    2.13  }
    2.14  
    2.15 +static void
    2.16 +X11_HandleClipboardEvent(_THIS, const XEvent *xevent)
    2.17 +{
    2.18 +    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
    2.19 +    Display *display = videodata->display;
    2.20 +
    2.21 +    SDL_assert(videodata->clipboard_window != None);
    2.22 +    SDL_assert(xevent->xany.window == videodata->clipboard_window);
    2.23 +
    2.24 +    switch (xevent->type) {
    2.25 +    /* Copy the selection from our own CUTBUFFER to the requested property */
    2.26 +        case SelectionRequest: {
    2.27 +            const XSelectionRequestEvent *req = &xevent->xselectionrequest;
    2.28 +            XEvent sevent;
    2.29 +            int seln_format;
    2.30 +            unsigned long nbytes;
    2.31 +            unsigned long overflow;
    2.32 +            unsigned char *seln_data;
    2.33 +
    2.34 +#ifdef DEBUG_XEVENTS
    2.35 +            printf("window CLIPBOARD: SelectionRequest (requestor = %ld, target = %ld)\n",
    2.36 +                req->requestor, req->target);
    2.37 +#endif
    2.38 +
    2.39 +            SDL_zero(sevent);
    2.40 +            sevent.xany.type = SelectionNotify;
    2.41 +            sevent.xselection.selection = req->selection;
    2.42 +            sevent.xselection.target = None;
    2.43 +            sevent.xselection.property = None;  /* tell them no by default */
    2.44 +            sevent.xselection.requestor = req->requestor;
    2.45 +            sevent.xselection.time = req->time;
    2.46 +
    2.47 +            /* !!! FIXME: We were probably storing this on the root window
    2.48 +               because an SDL window might go away...? but we don't have to do
    2.49 +               this now (or ever, really). */
    2.50 +            if (X11_XGetWindowProperty(display, DefaultRootWindow(display),
    2.51 +                    X11_GetSDLCutBufferClipboardType(display), 0, INT_MAX/4, False, req->target,
    2.52 +                    &sevent.xselection.target, &seln_format, &nbytes,
    2.53 +                    &overflow, &seln_data) == Success) {
    2.54 +                /* !!! FIXME: cache atoms */
    2.55 +                Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
    2.56 +                if (sevent.xselection.target == req->target) {
    2.57 +                    X11_XChangeProperty(display, req->requestor, req->property,
    2.58 +                        sevent.xselection.target, seln_format, PropModeReplace,
    2.59 +                        seln_data, nbytes);
    2.60 +                    sevent.xselection.property = req->property;
    2.61 +                } else if (XA_TARGETS == req->target) {
    2.62 +                    Atom SupportedFormats[] = { XA_TARGETS, sevent.xselection.target };
    2.63 +                    X11_XChangeProperty(display, req->requestor, req->property,
    2.64 +                        XA_ATOM, 32, PropModeReplace,
    2.65 +                        (unsigned char*)SupportedFormats,
    2.66 +                        SDL_arraysize(SupportedFormats));
    2.67 +                    sevent.xselection.property = req->property;
    2.68 +                    sevent.xselection.target = XA_TARGETS;
    2.69 +                }
    2.70 +                X11_XFree(seln_data);
    2.71 +            }
    2.72 +            X11_XSendEvent(display, req->requestor, False, 0, &sevent);
    2.73 +            X11_XSync(display, False);
    2.74 +        }
    2.75 +        break;
    2.76 +
    2.77 +        case SelectionNotify: {
    2.78 +#ifdef DEBUG_XEVENTS
    2.79 +            printf("window CLIPBOARD: SelectionNotify (requestor = %ld, target = %ld)\n",
    2.80 +                xevent.xselection.requestor, xevent.xselection.target);
    2.81 +#endif
    2.82 +            videodata->selection_waiting = SDL_FALSE;
    2.83 +        }
    2.84 +        break;
    2.85 +
    2.86 +        case SelectionClear: {
    2.87 +            /* !!! FIXME: cache atoms */
    2.88 +            Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
    2.89 +
    2.90 +#ifdef DEBUG_XEVENTS
    2.91 +            printf("window CLIPBOARD: SelectionClear (requestor = %ld, target = %ld)\n",
    2.92 +                xevent.xselection.requestor, xevent.xselection.target);
    2.93 +#endif
    2.94 +
    2.95 +            if (xevent->xselectionclear.selection == XA_PRIMARY ||
    2.96 +                (XA_CLIPBOARD != None && xevent->xselectionclear.selection == XA_CLIPBOARD)) {
    2.97 +                SDL_SendClipboardUpdate();
    2.98 +            }
    2.99 +        }
   2.100 +        break;
   2.101 +    }
   2.102 +}
   2.103 +
   2.104  
   2.105  static void
   2.106  X11_DispatchEvent(_THIS)
   2.107 @@ -615,6 +705,12 @@
   2.108             xevent.type, xevent.xany.display, xevent.xany.window);
   2.109  #endif
   2.110  
   2.111 +    if ((videodata->clipboard_window != None) &&
   2.112 +        (videodata->clipboard_window == xevent.xany.window)) {
   2.113 +        X11_HandleClipboardEvent(_this, &xevent);
   2.114 +        return;
   2.115 +    }
   2.116 +
   2.117      data = NULL;
   2.118      if (videodata && videodata->windowlist) {
   2.119          for (i = 0; i < videodata->numwindows; ++i) {
   2.120 @@ -1223,55 +1319,6 @@
   2.121          }
   2.122          break;
   2.123  
   2.124 -    /* Copy the selection from our own CUTBUFFER to the requested property */
   2.125 -    case SelectionRequest: {
   2.126 -            XSelectionRequestEvent *req;
   2.127 -            XEvent sevent;
   2.128 -            int seln_format;
   2.129 -            unsigned long nbytes;
   2.130 -            unsigned long overflow;
   2.131 -            unsigned char *seln_data;
   2.132 -
   2.133 -            req = &xevent.xselectionrequest;
   2.134 -#ifdef DEBUG_XEVENTS
   2.135 -            printf("window %p: SelectionRequest (requestor = %ld, target = %ld)\n", data,
   2.136 -                req->requestor, req->target);
   2.137 -#endif
   2.138 -
   2.139 -            SDL_zero(sevent);
   2.140 -            sevent.xany.type = SelectionNotify;
   2.141 -            sevent.xselection.selection = req->selection;
   2.142 -            sevent.xselection.target = None;
   2.143 -            sevent.xselection.property = None;
   2.144 -            sevent.xselection.requestor = req->requestor;
   2.145 -            sevent.xselection.time = req->time;
   2.146 -
   2.147 -            if (X11_XGetWindowProperty(display, DefaultRootWindow(display),
   2.148 -                    X11_GetSDLCutBufferClipboardType(display), 0, INT_MAX/4, False, req->target,
   2.149 -                    &sevent.xselection.target, &seln_format, &nbytes,
   2.150 -                    &overflow, &seln_data) == Success) {
   2.151 -                Atom XA_TARGETS = X11_XInternAtom(display, "TARGETS", 0);
   2.152 -                if (sevent.xselection.target == req->target) {
   2.153 -                    X11_XChangeProperty(display, req->requestor, req->property,
   2.154 -                        sevent.xselection.target, seln_format, PropModeReplace,
   2.155 -                        seln_data, nbytes);
   2.156 -                    sevent.xselection.property = req->property;
   2.157 -                } else if (XA_TARGETS == req->target) {
   2.158 -                    Atom SupportedFormats[] = { XA_TARGETS, sevent.xselection.target };
   2.159 -                    X11_XChangeProperty(display, req->requestor, req->property,
   2.160 -                        XA_ATOM, 32, PropModeReplace,
   2.161 -                        (unsigned char*)SupportedFormats,
   2.162 -                        SDL_arraysize(SupportedFormats));
   2.163 -                    sevent.xselection.property = req->property;
   2.164 -                    sevent.xselection.target = XA_TARGETS;
   2.165 -                }
   2.166 -                X11_XFree(seln_data);
   2.167 -            }
   2.168 -            X11_XSendEvent(display, req->requestor, False, 0, &sevent);
   2.169 -            X11_XSync(display, False);
   2.170 -        }
   2.171 -        break;
   2.172 -
   2.173      case SelectionNotify: {
   2.174              Atom target = xevent.xselection.target;
   2.175  #ifdef DEBUG_XEVENTS
   2.176 @@ -1315,19 +1362,6 @@
   2.177                  X11_XSendEvent(display, data->xdnd_source, False, NoEventMask, (XEvent*)&m);
   2.178  
   2.179                  X11_XSync(display, False);
   2.180 -
   2.181 -            } else {
   2.182 -                videodata->selection_waiting = SDL_FALSE;
   2.183 -            }
   2.184 -        }
   2.185 -        break;
   2.186 -
   2.187 -    case SelectionClear: {
   2.188 -            Atom XA_CLIPBOARD = X11_XInternAtom(display, "CLIPBOARD", 0);
   2.189 -
   2.190 -            if (xevent.xselectionclear.selection == XA_PRIMARY ||
   2.191 -                (XA_CLIPBOARD != None && xevent.xselectionclear.selection == XA_CLIPBOARD)) {
   2.192 -                SDL_SendClipboardUpdate();
   2.193              }
   2.194          }
   2.195          break;
     3.1 --- a/src/video/x11/SDL_x11video.c	Tue Aug 01 20:16:10 2017 -0700
     3.2 +++ b/src/video/x11/SDL_x11video.c	Mon Jul 31 13:49:22 2017 -0400
     3.3 @@ -449,6 +449,10 @@
     3.4  {
     3.5      SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
     3.6  
     3.7 +    if (data->clipboard_window) {
     3.8 +        X11_XDestroyWindow(data->display, data->clipboard_window);
     3.9 +    }
    3.10 +
    3.11      SDL_free(data->classname);
    3.12  #ifdef X_HAVE_UTF8_STRING
    3.13      if (data->im) {
     4.1 --- a/src/video/x11/SDL_x11video.h	Tue Aug 01 20:16:10 2017 -0700
     4.2 +++ b/src/video/x11/SDL_x11video.h	Mon Jul 31 13:49:22 2017 -0400
     4.3 @@ -82,6 +82,7 @@
     4.4      SDL_WindowData **windowlist;
     4.5      int windowlistlength;
     4.6      XID window_group;
     4.7 +    Window clipboard_window;
     4.8  
     4.9      /* This is true for ICCCM2.0-compliant window managers */
    4.10      SDL_bool net_wm;