Bug 228591 - Paste image from clipboard bad interoperability
Summary: Paste image from clipboard bad interoperability
Status: RESOLVED UPSTREAM
Alias: None
Product: kde
Classification: I don't know
Component: clipboard (show other bugs)
Version: unspecified
Platform: Ubuntu Linux
: NOR normal
Target Milestone: ---
Assignee: Lubos Lunak
URL:
Keywords:
: 245514 (view as bug list)
Depends on:
Blocks:
 
Reported: 2010-02-26 09:59 UTC by Aardwolf
Modified: 2010-10-05 14:29 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Aardwolf 2010-02-26 09:59:39 UTC
Version:            (using KDE 4.3.2)
OS:                Linux
Installed from:    Ubuntu Packages

When pasting clipboard data from a clipboard in X, it can use "INCR" transfer for large data.

I have reason to believe that KDE does not send such INCR data in a correct way. To reproduce:

Open Gimp.

Take a screenshot of something complex on screen with KDE by pressing CTRL+Print Screen.

Paste in Gimp.

The pasting in Gimp works if your screenshot has a small filesize. It does NOT work if your screenshot has large filesize.

In other words, to reproduce the bug for sure, have a very complex desktop wallpaper pattern, do "show desktop", and then press CTRL+Print Screen for the screenshot so that it compresses to a large PNG.

Paste in Gimp and it doesn't work. Paste in other programs that use X to get data from the clipboard and deal with INCR transfer, and it also won't work.

Paste in KolourPaint and it does work, but KolourPaint is from KDE itself so they probably use Qt or KDE API instead of X for image clipboard data...
Comment 1 Aardwolf 2010-02-26 10:12:00 UTC
I can reproduce this problem in KDE 3.5 and in KDE 4.X.

Also, below is C++ code that I use to get an image from the clipboard. The reason that I believe that the bug is in KDE or Qt and not in the code below, is the fact that first of all the code below works correct in Gnome, and second, the fact that Gimp shows the same problem as my code below (while written completely independent). The code below can be useful to try to reproduce the problem...

------------------------

#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);
}*/
Comment 2 Lubos Lunak 2010-03-01 17:06:13 UTC
KDE just uses QClipboard from Qt, so I suggest creating a Qt-only testcase and reporting the problem at http://bugreports.qt.nokia.com .
Comment 3 Christoph Feck 2010-10-05 14:29:16 UTC
*** Bug 245514 has been marked as a duplicate of this bug. ***