XLib - How to correctly cancel the process of incremental data transfer?

Viewed 120

I have read this and looked for in many other places over the Internet, but I can't find the answer to my question. In a C++ program I have a class Clipboard. This class is monitoring X11 selection events and fetches the selection content into a string member when it changes. For large selections X11 uses the 'INCR transfer' mechanism. This process is described in the above link. When fetching the content of a large selection, I want to check if the selection size exceeds some limit(let's say, 20 megabytes), and if it does, I do not want this selection to be fetched to avoid inadequate memory consumption by my program. And this is roughly how I do this at the moment:

void Clipboard::fetchLargeSelectionContent()
{
    /* We have received a property of type 'INCR' and are now going to start
       the read process 
       Variables ending with '_' are class' private members */
    Atom clipboard = XInternAtom(display_, "CLIPBOARD", false);
    Atom target = XInternAtom(display_, "UTF8_STRING", false);
    unsigned long nItemsToRead = 0;
    Atom typeReturned = None;
    int formatReturned = 0;
    unsigned long nItemsReturned = 0;
    unsigned long nBytesRemaining = 0;
    unsigned char* dataReturned = nullptr;
    // We do not want selection to exceed 20 megabytes
    std::string::size_type sizeLimit = 20 * 1024 * 1024;
    bool maxSizeExceeded = false;
    // Listen for property events on the requestor's(us) window
    XSelectInput(display_, window_, PropertyChangeMask);
    /* The xlib::getCurrentTime() function is defined in a separate header file
       and returns the current X server time */
    Time propertyReadTime = xlib::getCurrentTime();
    /* Notify the selection owner that we are ready to receive the data
       by deleting the property of type 'INCR' */
    XDeleteProperty(display_, window_, property_);
    while (true)
    {
        XEvent event{};
        XNextEvent(display_, &event);
        if (event.type == PropertyNotify &&
                event.xproperty.state == PropertyNewValue &&
                event.xproperty.display == display_ &&
                event.xproperty.window == window_ &&
                event.xproperty.time >= propertyReadTime)
        {
            // Read zero bytes from the property just to get the size of its content
            XGetWindowProperty(
                    display_,
                    window_,
                    property_,
                    0,
                    0,
                    false,
                    target,
                    &typeReturned,
                    &formatReturned,
                    &nItemsReturned,
                    &nBytesRemaining,
                    &dataReturned);
            XFree(dataReturned);
            dataReturned = nullptr;
            if (nBytesRemaining == 0)
            {
                // Transfer completed
                XDeleteProperty(display_, window_, property_);
                break;
            }
            nItemsToRead = nBytesRemaining;
            propertyReadTime = xlib::getCurrentTime();
            // Read the content and delete the property requesting a new data chunk
            XGetWindowProperty(
                    display_,
                    window_,
                    property_,
                    0,
                    nItemsToRead,
                    true,
                    target,
                    &typeReturned,
                    &formatReturned,
                    &nItemsReturned,
                    &nBytesRemaining,
                    &dataReturned);
            if (!maxSizeExceeded)
            {
                content_ += std::string(
                        reinterpret_cast<const char*>(dataReturned),
                        nItemsReturned);
                maxSizeExceeded = content_.size() > sizeLimit;
            }
            XFree(dataReturned);
            dataReturned = nullptr;
        }
    }
    if (maxSizeExceeded)
    {
        content_.clear();
        std::string().swap(content_);
    }
    XSelectInput(display_, window_, NoEventMask);
}

This approach works, but there is an obvious downside with it - the data will still be received even after the sizeLimit has been exceeded, it just won't be written to content_. But why would I want to receive the data that I am not going to store and use? I need to somehow notify the selection owner that I am not interesting in receiving new data chunks anymore, and it should abort the INCR transfer process. But I have not found a way to do it. I've tried to simply replace while (true) with while (!maxSizeExceeded) to break the loop immediately after exceeding sizeLimit. But it seems that the selection owner is still waiting for my program to read the data it sent, because I can see significant growth of system memory usage with every consequent copy of 500MB text data. It doesn't happen if using the approach from my example though(read the entire data and just ignore it if it is too large in size).

So, the question is in the title - how to gracefully(correctly) abort the process of incremental data transfer, and is there even any way to do it?

0 Answers
Related