Implement INCR property mechanism for large data transfers [XCB]

Implement INCR property mechanism according to the icccm specification.

Change-Id: Ic8f85b71cab825d70ee1b61f29acd09fa4c3e642
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
bb10
Gatis Paeglis 2013-09-09 14:20:50 +02:00 committed by The Qt Project
parent fdd843d5a7
commit bc6f5b3ff6
3 changed files with 138 additions and 4 deletions

View File

@ -157,12 +157,121 @@ private:
QByteArray format_atoms;
};
class INCRTransaction;
typedef QMap<xcb_window_t,INCRTransaction*> TransactionMap;
static TransactionMap *transactions = 0;
//#define INCR_DEBUG
class INCRTransaction : public QObject
{
Q_OBJECT
public:
INCRTransaction(QXcbConnection *c, xcb_window_t w, xcb_atom_t p,
QByteArray d, uint i, xcb_atom_t t, int f, int to) :
conn(c), win(w), property(p), data(d), increment(i),
target(t), format(f), timeout(to), offset(0)
{
const quint32 values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
xcb_change_window_attributes(conn->xcb_connection(), win,
XCB_CW_EVENT_MASK, values);
if (!transactions) {
#ifdef INCR_DEBUG
qDebug("INCRTransaction: creating the TransactionMap");
#endif
transactions = new TransactionMap;
conn->clipboard()->setProcessIncr(true);
}
transactions->insert(win, this);
abort_timer = startTimer(timeout);
}
~INCRTransaction()
{
if (abort_timer)
killTimer(abort_timer);
abort_timer = 0;
transactions->remove(win);
if (transactions->isEmpty()) {
#ifdef INCR_DEBUG
qDebug("INCRTransaction: no more INCR transactions left in the TransactionMap");
#endif
delete transactions;
transactions = 0;
conn->clipboard()->setProcessIncr(false);
}
}
void updateIncrProperty(xcb_property_notify_event_t *event, bool &accepted)
{
xcb_connection_t *c = conn->xcb_connection();
if (event->atom == property && event->state == XCB_PROPERTY_DELETE) {
accepted = true;
// restart the timer
if (abort_timer)
killTimer(abort_timer);
abort_timer = startTimer(timeout);
unsigned int bytes_left = data.size() - offset;
if (bytes_left > 0) {
unsigned int bytes_to_send = qMin(increment, bytes_left);
#ifdef INCR_DEBUG
qDebug("INCRTransaction: sending %d bytes, %d remaining (INCR transaction %p)",
bytes_to_send, bytes_left - bytes_to_send, this);
#endif
int dataSize = bytes_to_send / (format / 8);
xcb_change_property(c, XCB_PROP_MODE_REPLACE, win, property,
target, format, dataSize, data.constData() + offset);
offset += bytes_to_send;
} else {
#ifdef INCR_DEBUG
qDebug("INCRTransaction: INCR transaction %p completed", this);
#endif
xcb_change_property(c, XCB_PROP_MODE_REPLACE, win, property,
target, format, 0, (const void *)0);
const quint32 values[] = { XCB_EVENT_MASK_NO_EVENT };
xcb_change_window_attributes(conn->xcb_connection(), win,
XCB_CW_EVENT_MASK, values);
// self destroy
delete this;
}
}
}
protected:
void timerEvent(QTimerEvent *ev)
{
if (ev->timerId() == abort_timer) {
// this can happen when the X client we are sending data
// to decides to exit (normally or abnormally)
#ifdef INCR_DEBUG
qDebug("INCRTransaction: Timed out while sending data to %p", this);
#endif
delete this;
}
}
private:
QXcbConnection *conn;
xcb_window_t win;
xcb_atom_t property;
QByteArray data;
uint increment;
xcb_atom_t target;
int format;
int timeout;
uint offset;
int abort_timer;
};
const int QXcbClipboard::clipboard_timeout = 5000;
QXcbClipboard::QXcbClipboard(QXcbConnection *c)
: QXcbObject(c), QPlatformClipboard()
, m_requestor(XCB_NONE)
, m_owner(XCB_NONE)
, m_incr_active(false)
, m_clipboard_closing(false)
{
Q_ASSERT(QClipboard::Clipboard == 0);
Q_ASSERT(QClipboard::Selection == 1);
@ -200,6 +309,7 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c)
QXcbClipboard::~QXcbClipboard()
{
m_clipboard_closing = true;
// Transfer the clipboard content to the clipboard manager if we own a selection
if (m_timestamp[QClipboard::Clipboard] != XCB_CURRENT_TIME ||
m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
@ -224,6 +334,17 @@ QXcbClipboard::~QXcbClipboard()
}
}
void QXcbClipboard::incrTransactionPeeker(xcb_generic_event_t *ge, bool &accepted)
{
uint response_type = ge->response_type & ~0x80;
if (response_type == XCB_PROPERTY_NOTIFY) {
xcb_property_notify_event_t *event = (xcb_property_notify_event_t *)ge;
TransactionMap::Iterator it = transactions->find(event->window);
if (it != transactions->end()) {
(*it)->updateIncrProperty(event, accepted);
}
}
}
xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
{
@ -415,16 +536,17 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
// Motif clients (since Motif doesn't support INCR)
static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::CLIP_TEMPORARY);
bool allow_incr = property != motif_clip_temporary;
// This 'bool' can be removed once there is a proper fix for QTBUG-32853
if (m_clipboard_closing)
allow_incr = false;
// X_ChangeProperty protocol request is 24 bytes
const int increment = (xcb_get_maximum_request_length(xcb_connection()) * 4) - 24;
if (data.size() > increment && allow_incr) {
long bytes = data.size();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
// (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
qWarning("QXcbClipboard: INCR is unimplemented");
new INCRTransaction(connection(), window, property, data, increment,
atomFormat, dataFormat, clipboard_timeout);
return property;
}

View File

@ -83,6 +83,10 @@ public:
QByteArray getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom);
void setProcessIncr(bool process) { m_incr_active = process; }
bool processIncr() { return m_incr_active; }
void incrTransactionPeeker(xcb_generic_event_t *ge, bool &accepted);
xcb_window_t getSelectionOwner(xcb_atom_t atom) const;
QByteArray getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t = 0);
@ -107,6 +111,8 @@ private:
static const int clipboard_timeout;
bool m_incr_active;
bool m_clipboard_closing;
};
#endif // QT_NO_CLIPBOARD

View File

@ -1154,6 +1154,12 @@ void QXcbConnection::processXcbEvents()
continue;
}
bool accepted = false;
if (clipboard()->processIncr())
clipboard()->incrTransactionPeeker(event, accepted);
if (accepted)
continue;
QVector<PeekFunc>::iterator it = m_peekFuncs.begin();
while (it != m_peekFuncs.end()) {
// These callbacks return true if the event is what they were