CLIPBOARD selection doesn't save content

Quintus sutniuq at
Mon Apr 5 04:44:12 PDT 2010

Hi all,

I managed to get it work now! Many thanks to you all, without the
resources you pointed me to I wouldn't have been able to finish this.

So, here's the code:
*  Clipboard.write(clipboard, text) ==> nil
*Writes +text+ to the specified clipboard.
static VALUE m_write(VALUE self, VALUE rclipboard, VALUE rtext) /*VALUE
is a Ruby object*/
  Display * p_display;
  Window win, clipboard_owner;
  XEvent xevt, xevt2;
  Atom targets[6], target_sizes[12], save_targets[2];

  /*Ignore Ruby parameters as we're just debugging*/

  /*Open default display*/
  p_display = XOpenDisplay(NULL);
  /*Let Ruby handle protocol errors*/
  /*This are the TARGETS we support*/
  targets[0] = TARGETS_ATOM;
  targets[1] = UTF8_ATOM;
  targets[2] = XA_STRING;
  targets[3] = XInternAtom(p_display, "TIMESTAMP", True); /*TODO:
Implement this request*/
  targets[4] = SAVE_TARGETS_ATOM;
  targets[5] = TARGET_SIZES_ATOM;
  /*These are the target's sizes*/
  target_sizes[0] = TARGETS_ATOM;
  target_sizes[1] = 6 * sizeof(Atom);

  target_sizes[2] = UTF8_ATOM;
  target_sizes[3] = 4;

  target_sizes[4] = XA_STRING;
  target_sizes[5] = 4;

  target_sizes[6] = XInternAtom(p_display, "TIMESTAMP", True);
  target_sizes[7] = -1;

  target_sizes[8] = SAVE_TARGETS_ATOM;
  target_sizes[9] = -1;

  target_sizes[10] = TARGET_SIZES_ATOM;
  target_sizes[11] = 12 * sizeof(Atom);
  /*We want our data have stored as UTF-8*/
  save_targets[0] = UTF8_ATOM;
  save_targets[1] = XA_STRING;
    rb_raise(XError, "No clipboard manager available!"); /*Throw Ruby

  /*Output some useful information*/
  printf("PID: %i\n", getpid());
  printf("Owner of CLIPBOARD_MANAGER: %lu\n",
XGetSelectionOwner(p_display, CLIPBOARD_MANAGER_ATOM));

  /*Create a window for copying into CLIPBOARD*/

  if ( (clipboard_owner = XGetSelectionOwner(p_display,
    rb_raise(XError, "No owner for the CLIPBOARD_MANAGER selection!");
/*Throw Ruby error*/

  /*Get control of the CLIPBOARD*/
  XSetSelectionOwner(p_display, CLIPBOARD_ATOM, win, CurrentTime);

  /*Our application "needs to exit"*/
  XChangeProperty(p_display, win, IMITATOR_X_CLIP_ATOM, XA_ATOM, 32,
PropModeReplace, (unsigned char *) save_targets, 1);
  XConvertSelection(p_display, CLIPBOARD_MANAGER_ATOM,
  for (;;)
    XNextEvent(p_display, &xevt);
    /**********************DEBUGGING INFORMATION***************************/
    if (xevt.type == SelectionRequest)
      printf("SelectionRequest event; Requested target: %s\n",
      printf("Requestor: %lu\n", xevt.xselectionrequest.requestor);
    else if (xevt.type == SelectionNotify)
      printf("SelectionNotify event; property: %s\n",
      printf("Requestor: %lu\n", xevt.xselection.requestor);
    /**********************END DEBUGGING INFORMATION*********************/
    if (xevt.type == SelectionRequest) /*selection-related event*/
      /*This is for all anserwing events the same (except "not supported")*/
      xevt2.xselection.type = SelectionNotify;
      xevt2.xselection.display = xevt.xselectionrequest.display;
      xevt2.xselection.requestor = xevt.xselectionrequest.requestor;
      xevt2.xselection.selection = xevt.xselectionrequest.selection; =;
      xevt2.xselection.time = xevt.xselectionrequest.time; =;

      /*Handle individual selection requests*/
      if ( == XInternAtom(p_display,
"TARGETS", True)) /*TARGETS information requested*/
        /*Write supported TARGETS into the requestor*/
        XChangeProperty(p_display, xevt.xselectionrequest.requestor,, XA_ATOM, 32, PropModeReplace, (unsigned
char *) targets, 4);
        /*Notify the requestor we have set its requested property*/
        XSendEvent(p_display, xevt.xselectionrequest.requestor, False,
NoEventMask, &xevt2);
      else if ( == TARGET_SIZES_ATOM)
        /*Answer how much data we want to store*/
        XChangeProperty(p_display, xevt.xselectionrequest.requestor,, ATOM_PAIR_ATOM, 32, PropModeReplace,
(unsigned char *) target_sizes, 6);
        /*Notify the requestor we have set its requested property*/
        XSendEvent(p_display, xevt.xselectionrequest.requestor, False,
NoEventMask, &xevt2);
      else if ( == MULTIPLE_ATOM) /*Makes
multiple requests at once*/
        Atom actual_type, requested = None;
        int actual_format, i;
        unsigned long nitems, bytes;
        unsigned char * prop;
        Atom * wanted_atoms;
        /*See how much data is there and allocate this amount*/
        XGetWindowProperty(p_display, xevt.xselectionrequest.requestor,, 0, 0, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes, &prop);
        wanted_atoms = (Atom *) malloc(sizeof(Atom) * nitems);
        /*Now get the data and copy it to our variable*/
        XGetWindowProperty(p_display, xevt.xselectionrequest.requestor,, 0, 1000000, False, AnyPropertyType,
&actual_type, &actual_format, &nitems, &bytes, &prop);
        memcpy(wanted_atoms, prop, sizeof(Atom) * nitems);
        /*Now handle each single request by it's own*/
        for(i = 0;i < nitems; i++)
          if (requested == None) /*This means we'll get a target atom*/
            requested = wanted_atoms[i];
          else /*This means we'll get a property atom*/
            /*OK, I see. I could have made the event handling a separate
function and then call it recursively here,
            *but since I only support two targets that's unneccessary I
            if (requested == UTF8_ATOM || requested == XA_STRING)
/*Answer the request*/
xevt.xselectionrequest.requestor, wanted_atoms[i], requested, 32,
PropModeReplace, (unsigned char *) "TEST", 4);
            else /*Not supported request*/
xevt.xselectionrequest.requestor, wanted_atoms[i], requested, 32,
PropModeReplace, (unsigned char *) None, 1);
            requested = None; /*The next iteration will be a target atom
        /*Notify the requestor we have finished*/
        XSendEvent(p_display, xevt.xselectionrequest.requestor, False,
NoEventMask, &xevt2);
        /*Free the allocated memory*/
      else if ( == UTF8_ATOM || == XA_STRING) /*UTF-8 or ASCII requested*/
        /*Write the string "TEST" into the requestor*/
        XChangeProperty(p_display, xevt.xselectionrequest.requestor,,, 8,
PropModeReplace, (unsigned char *) "TEST", 4);
        /*Notify the requestor we've finished*/
        XSendEvent(p_display, xevt.xselectionrequest.requestor, False,
NoEventMask, &xevt2);
      else /*No supported request. SAVE_TARGETS is included here, since
it's only a marker*/
        /*Notify the requestor we don't support what it wants*/ = None; /*This indicates we don't
support what it wants*/
        XSendEvent(p_display, xevt.xselectionrequest.requestor, False,
NoEventMask, &xevt2);
    else if (xevt.type == SelectionNotify) /*OK, our request to the
clipboard manager has completed*/
      if ( == None) /*Ooops - conversion failed,
we're still the owner of CLIPBOARD*/
        rb_raise(XError, "Unable to request the clipboard manager to
acquire the CLIPBOARD selection!"); /*Throw Ruby error*/
      else if ( == IMITATOR_X_CLIP_ATOM)
/*Success - we're out of responsibility now and can safely exit*/

  /*Cleanup actions*/
  XDestroyWindow(p_display, win);

  /*Return Ruby nil object*/
  return Qnil;
And here's the header file:

/*All the macros defined here assume that you have an open X server
connection stored
*in a variable p_display. */

/*UTF-8 X-Encoding atom*/
#define UTF8_ATOM XInternAtom(p_display, "UTF8_STRING", True)

/*Atom for the CLIPBOARD selection*/
#define CLIPBOARD_ATOM XInternAtom(p_display, "CLIPBOARD", True)

#define CLIPBOARD_MANAGER_ATOM XInternAtom(p_display,

/*ATOM_PAIR atom*/
#define ATOM_PAIR_ATOM XInternAtom(p_display, "ATOM_PAIR", True)

#define SAVE_TARGETS_ATOM XInternAtom(p_display, "SAVE_TARGETS", True)

#define TARGET_SIZES_ATOM XInternAtom(p_display, "TARGET_SIZES", True)

/*TARGETS atom*/
#define TARGETS_ATOM XInternAtom(p_display, "TARGETS", True)

/*MULTIPLE request atom*/
#define MULTIPLE_ATOM XInternAtom(p_display, "MULTIPLE", True)

/*Atom for storing properties used by this library*/
#define IMITATOR_X_CLIP_ATOM XInternAtom(p_display, "IMITATOR_X_CLIP",

/*In order to work with the X selections, we need a window.
*This macro just creates a simple, unmapped window that is used for
*the X selection interaction*/
#define CREATE_REQUESTOR_WIN XCreateSimpleWindow(p_display,
XDefaultRootWindow(p_display), 0, 0, 1, 1, 0, 0, 0)

VALUE Clipboard;
void Init_clipboard(void);


Although it's a Ruby C extension, there's almost no Ruby-specific code
in it (beside the rb_raise() statements), so it may be helpful for those
trying to interact with a clipboard manager.

Well, I have to say that this is quite untested. It works on my Ubuntu
Karmic, but I'm going to test it under Xubuntu and openSUSE and
hopefully it will work.
Another point is the TARGET_SIZES request. I implemented it, but I never
got a request for this, so it may doesn't work.

One last thing I have to mention: After I acquried ownership of the
CLIPBOARD selection, I wasn't sent a TARGET request as I thought.
Instead, the clipboard manager (I checked the window IDs, it _was_ the
window manager) directly sent me a MULTIPLE request.

Thank you!

Glynn Clements schrieb:
> Yes. From the X server's perspective, a function which opens a
> connection, runs an event loop then closes the connection is
> indistinguishable from a more typical client. Successive calls to that
> function would appear as distinct short-lived clients. The server only
> sees connections; what happens on the client side of those connections
> is irrelevant.

More information about the xorg mailing list