Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-8644

clipboard INCR transfer to X applications doesn't work, even if they support INCR

    XMLWordPrintable

Details

    • Bug
    • Resolution: Done
    • P2: Important
    • 4.7.2
    • 3.x, 4.0.0, 4.0.1, 4.1.0, 4.1.1, 4.1.2, 4.1.3, 4.1.4, 4.1.5, 4.2.0, 4.2.1, 4.2.2, 4.2.3, 4.3.0, 4.3.1, 4.3.2, 4.3.3, 4.3.4, 4.3.5, 4.4.0, 4.4.1, 4.4.2, 4.4.3, 4.5.0, 4.5.1, 4.5.2, 4.5.3, 4.6.0, 4.6.1, 4.6.2
    • None
    • None
    • 62ee836fafe515c03b0be716585f8c2354e188f7, 4b81cb847647450f4bad8a0d9a278d43ebdfecc6

    Description

      In Linux environments, QClipboard seems to not send a certain X Event that non-Qt applications are waiting for, when using INCR transfer from the clipboard to the application.

      I first reported this bug in KDE. KDE Says they use QClipboard and I should report it here instead. The original KDE bug report is here:

      https://bugs.kde.org/show_bug.cgi?id=228591

      To reproduce, paste large image data from a Qt application (e.g. KolourPaint or take screenshot in KDE), to a non-Qt application, such as Gimp. It has to be large image data, because depending on the size of the image data, the clipboard will use the "easy" transfer, or "INCR" transfer.

      For example, take a screenshot of desktop in KDE, then try to paste it in Gimp. Gimp will wait several seconds and then bail out.

      This is not because Gimp doesn't support INCR transfer. Indeed it does support it, because non-Qt applications that use INCR transfer can paste to Gimp and vice-versa. For example a screenshot taken in Gnome can always be posted in Gimp.

      I had implemented C++ code myself to use the X clipboard system to paste data, and that is how I found that in Gnome it worked and in KDE (or Qt) it didn't. I followed steps described by X manuals to implement this INCR transfer. Basically, at a certain point you need to do getNextEvent and some event should come up giving you the next INCR data, but, while non-Qt apps send it, Qt applications don't.

      The code I use, which can be used to try this out, is pasted at the bottom.

      I'm not claiming this code is fully correct. It's the fact that I followed steps described by documents about X clipboard (links in the comments in the code below), and also the fact that Gimp, which is developed completely independently from, and has nothing to do with, the code below, has the same problem. Also, the code below can correctly receive INCR transfers in Gnome.

      Since Qt apps like KolourPaint seem to be able to receive INCR transfers from other Qt apps, what is the difference between the way QClipboard receives INCR transfers, and the code below and/or Gimp do this? Could this be seen as a bug since it appears to mean that the interoperability between Qt and non-Qt X applications isn't so good? If not a bug, is there any documentation related to how Qt sends INCR transfers since it does it in a different than non-Qt apps?

      #include <X11/Xlib.h>
      #include <X11/Xatom.h>
      #include <cstdio>
      #include <climits>
      #include <cstring>
      #include <iostream>
      
      /*
      This code is created thanks to the "ad-hoc" tutorials from
      http://mi.eng.cam.ac.uk/~er258/code/x11.html,
      but written from scratch.
      Also used for documentation:
      http://tronche.com/gui/x/icccm/sec-2.html
      xsel.c (search for a recent enough version of it that has INCR related code in
      it)
      */
      
      //the return values of XGetWindowProperty
      struct WindowProperty
      {
        unsigned char* prop;
        int actual_format;
        int nitems;
        Atom actual_type;
      
        WindowProperty()
        : prop(0)
        {
        }
      
        ~WindowProperty()
        {
          if(prop) XFree(prop);
        }
      };
      
      std::string GetAtomName(Display* display, Atom atom)
      {
        if(atom == None) return "None";
        char* name = XGetAtomName(display, atom);
        std::string result(name);
        XFree(name);
        return result;
      }
      
      void getWindowProperty(WindowProperty& property, Display* display, const
      Window& window, Atom atom, Bool del)
      {
        Atom actual_type;
        int actual_format;
        unsigned long nitems;
        unsigned long bytes_after;
        unsigned char* prop = 0;
      
        int length = 1024;
      
        //read all bytes
        do
        {
          if(prop) XFree(prop);
          XGetWindowProperty(display, window, atom, del, length, False,
      AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
          length *= 2;
        } while(bytes_after != 0);
      
        property.prop = prop;
        property.actual_format = actual_format;
        property.nitems = nitems;
        property.actual_type = actual_type;
      }
      
      Atom findAtomOfType(Display* display, const WindowProperty& property, const
      std::string& datatype)
      {
        Atom* atoms = (Atom*)property.prop;
      
      for(int i = 0; i < property.nitems; i++) std::cout << "available target type: "
      << GetAtomName(display, atoms[i]) << std::endl;
      
        for(int i = 0; i < property.nitems; i++)
        {
          if(GetAtomName(display, atoms[i]) == datatype) return atoms[i];
        }
      
        return None;
      }
      
      bool getNextEvent(Display *display, XEvent *event_return)
      {
      #if 1
      //This one doesn't always get the event
        double t = getSeconds();
        while(!XPending(display))
        {
          if(getSeconds() - t > 5.0)
          {
      std::cout << "Error: The XNextEvent never came... :(" << std::endl;
            return false; //he's probably never going to send us an event :(
          }
        }
        XNextEvent(display, event_return);
        return true;
      #else
      //This one blocks forever when seeing complex visuals fullscreen (so > 300K
      data), pressing ctrl + printscreen, and then pasting it in the program, causing
      INCR transfer
      //Gimp also hangs in this scenario. And not in Gnome. So KDE is the bug!
        XNextEvent(display, event_return);
        return true;
      #endif
      }
      
      void getIncrData(std::vector<unsigned char>& data,const XSelectionEvent&
      selevent)
      {
      std::cout << "Incremental transfer starting due to INCR property" << std::endl;
        XEvent event;
        XSelectInput(selevent.display, selevent.requestor, PropertyChangeMask);
        XDeleteProperty(selevent.display, selevent.requestor, selevent.property);
      //this SHOULD start the INCR mechanism (but in KDE 3.5 I don't get any events
      after this???)
      
        for(;;)
        {
          if(!getNextEvent(selevent.display, &event)) break;
          if(event.type == PropertyNotify)
          {
            if (event.xproperty.state != PropertyNewValue) continue;
            WindowProperty property;
            getWindowProperty(property, selevent.display, selevent.requestor,
      selevent.property, False);
            size_t num_bytes = property.nitems * property.actual_format / 8;
      std::cout<<"INCR data size: " << num_bytes << std::endl;
            for(size_t i = 0; i < num_bytes; i++) data.push_back(property.prop[i]);
            XDeleteProperty(selevent.display, selevent.requestor, selevent.property);
            if(num_bytes == 0) break;
          }
          else break;
        }
      }
      
      
      //stores the image as RGBA in image, and its width and height in w and h. So
      image.size() is w * h * 4. Returns true if there was an image on the clipboard,
      false if not (in that case the output parameters should not be used)
      bool getClipboardImage(std::vector<unsigned char>& image, int& w, int& h)
      {
        Display* display = XOpenDisplay(NULL);
        int screen = DefaultScreen(display);
        Window root = RootWindow(display, screen);
      
        std::string datatype = "image/png"; //datatype can be something like STRING,
      image/png, ... We use "image/png" here, and that is used by lots of linux
      programs luckily so it works almost always for images.
      
        //dummy window
        Window window = XCreateSimpleWindow(display, root, 0, 0, 100, 100, 0,
      BlackPixel(display, screen), BlackPixel(display, screen));
      
        Atom ATOM_TARGETS = XInternAtom(display, "TARGETS", False); //possible
      formats in which source program can output the data
        Atom ATOM_CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
      
        XConvertSelection(display, ATOM_CLIPBOARD, ATOM_TARGETS, ATOM_CLIPBOARD,
      window, CurrentTime);
        XFlush(display);
      
        std::vector<unsigned char> data;
      
        XEvent event;
        bool sent_request = false;
        Atom image_png_atom = None;
        for(;;)
        {
          if(!getNextEvent(display, &event)) break;
          if(event.type == SelectionNotify)
          {
            Atom target = event.xselection.target;
      
      std::cout << "target atom name: " << GetAtomName(display, target) << std::endl;
      
            if(event.xselection.property != None)
            {
              WindowProperty property;
              getWindowProperty(property, display, window, ATOM_CLIPBOARD, False);
      
      std::cout << "property atom name: " << GetAtomName(display,
      property.actual_type) << std::endl;
      
              if(target == ATOM_TARGETS && !sent_request)
              {
                //property.prop now contains a list of Atoms, and each atom has a
      datatype associated with it
                sent_request = true;
                image_png_atom = findAtomOfType(display, property, "image/png");
      
                if(image_png_atom != None) XConvertSelection(display, ATOM_CLIPBOARD,
      image_png_atom, ATOM_CLIPBOARD, window, CurrentTime);
                else break;
              }
              else if(target == image_png_atom)
              {
                if(GetAtomName(display, property.actual_type) == "image/png")
                {
                  //property.prop now contains actual data bytes, the image
                  size_t num_bytes = property.nitems * property.actual_format / 8;
      std::cout<<"data size: " << num_bytes << std::endl;
                  data.resize(num_bytes);
                  for(size_t i = 0; i < data.size(); i++) data[i] = property.prop[i];
                  break;
                }
                else if(GetAtomName(display, property.actual_type) == "INCR")
                {
                  //XConvertSelection(display, ATOM_CLIPBOARD, ATOM_TARGETS,
      ATOM_CLIPBOARD, window, CurrentTime);
                  //XFlush(display);
                  getIncrData(data, event.xselection);
                  break;
                }
              }
              else break;
            }
            else break;
          }
        }
      
        if(!data.empty())
        {
          //Decode the PNG data here, store the image pixels as RGBA in the output
      parameters image, w and h
      std::cout << successfully got full PNG image << std::endl;
          return true;
        }
      std::cout<<"no image on clipboard (or error)" << std::endl;
        return false;
      }
      
      /*int main()
      {
        std::vector<unsigned char> image;
        int w, h;
        getClipboardImage(image, w, h);
      }*/
      

      Attachments

        Issue Links

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              dzyubenk Denis Dzyubenko (Inactive)
              aardwolf Aardwolf
              Votes:
              2 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Gerrit Reviews

                  There are no open Gerrit changes