Details
-
Bug
-
Resolution: Done
-
P2: Important
-
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
- replaces
-
QTBUG-11085 QClipboard: Timed out while sending data when pasting into firefox
- Closed