This is an automated email from the git hooks/post-receive script. x2go pushed a change to branch master in repository x2gokdriveclient. from a490711 give server some time for initialization before sending version. new fa64159 support sending and recieving selections on demand. Support reading and writing INCR properties. The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Summary of changes: client.cpp | 299 +++++++++++++++++++++++++++--------- client.h | 37 +++-- debian/changelog | 1 + xcbclip.cpp | 457 ++++++++++++++++++++++++++++++++++++++++++++++--------- xcbclip.h | 59 ++++++- 5 files changed, 691 insertions(+), 162 deletions(-) -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdriveclient.git
This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository x2gokdriveclient. commit fa6415964959d733e3d32d6096b4a52f65d50b60 Author: Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> Date: Wed Sep 30 11:36:36 2020 -0500 support sending and recieving selections on demand. Support reading and writing INCR properties. --- client.cpp | 299 +++++++++++++++++++++++++++--------- client.h | 37 +++-- debian/changelog | 1 + xcbclip.cpp | 457 ++++++++++++++++++++++++++++++++++++++++++++++--------- xcbclip.h | 59 ++++++- 5 files changed, 691 insertions(+), 162 deletions(-) diff --git a/client.cpp b/client.cpp index e5fd37e..370b104 100644 --- a/client.cpp +++ b/client.cpp @@ -113,7 +113,7 @@ OutputChunk::OutputChunk(SelectionType selection, SelectionMime mime) { this->selection=selection; mimeData=mime; - compressed=false; + totalSize=0; firstChunk=lastChunk=false; } @@ -801,9 +801,26 @@ void Client::setCursor() void Client::getServerversion() { serverVersion=*((uint16_t*)messageBuffer+2); + serverExtSelection = (serverVersion>1); + qDebug()<<"server version:"<<serverVersion; } +void Client::getClientSelection() +{ + uint16_t sel=*((uint16_t*)messageBuffer+2); + SelectionType selection=PRIMARY; + if(sel) + selection=CLIPBOARD; + qDebug()<<"server demands data for "<<selection; +#ifdef Q_OS_LINUX + clipboard->requestSelectionData(selection); +#else + sendSelectionToServer(selection); +#endif + +} + void Client::getCursor() @@ -843,40 +860,72 @@ void Client::getCursor() void Client::getSelectionBuffer() { #ifndef Q_OS_LINUX -#warning check it //if using qt class, not supporting on demand load of data - QClipboard::Mode mode=QClipboard::Clipboard; - if(!selectionClipboard) + setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed_size, selectionSize, messageBuffer); +#else + clipboard->setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed_size, selectionSize, messageBuffer); +#endif + freeMessageBuffer(); +} + + +#ifndef Q_OS_LINUX +void Client::setInputSelectionData(SelectionType, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify) +{ + //if notify is true, we don't have actual data, just notification + //copy data to selection buffer + // qDebug()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size<<notify; + + + if(firstChunk) { - mode=QClipboard::Selection; - qDebug()<<"Got new Primary selection, format:"<<selectionFormat; + selData.clear(); + selMime=mime; + total_compressed=0; } + + if(!compressed) + selData.append(data,size); else - qDebug()<<"Got new Clipboard, format:"<<selectionFormat; + { + QByteArray ba; + ba.append((char*) &size, 4); + ba.append(data, compressed); - QClipboard* clipboard=QGuiApplication::clipboard(); + total_compressed+=compressed; + selData.append(qUncompress(ba)); + // qDebug()<<"uncompress from "<<compressed<<" to "<<size; + } -// qDebug()<<messageBuffer; - switch(selectionFormat) + if(lastChunk ) { - case STRING: clipboard->setText(QString::fromLocal8Bit(messageBuffer, selectionSize), mode); break; - case UTF_STRING: clipboard->setText(QString::fromUtf8(messageBuffer, selectionSize), mode);break; - case PIXMAP: + if(notify) { - QPixmap pix; - pix.loadFromData((const uchar*)messageBuffer, selectionSize); -// qDebug()<<pix; - clipboard->setImage(pix.toImage(), mode); - break; + qDebug()<<"Got selection notify from server"; + } + else + { +// qDebug()<<"total size: "<<selData.size()<<"compressed size"<<total_compressed; + QClipboard* clipboard=QGuiApplication::clipboard(); + QClipboard::Mode mode=QClipboard::Clipboard; + switch(selectionFormat) + { + case STRING: clipboard->setText(QString::fromLocal8Bit(selData), mode); break; + case UTF_STRING: clipboard->setText(QString::fromUtf8(selData), mode);break; + case PIXMAP: + { + QPixmap pix; + pix.loadFromData(selData); + clipboard->setImage(pix.toImage(), mode); + break; + } + } } } -#else - clipboard->setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed, selectionSize, messageBuffer); -#endif - freeMessageBuffer(); } +#endif void Client::getDeletedCursorsList() { @@ -951,20 +1000,44 @@ void Client::getSelection() { firstChunk=*((uint32_t*)messageBuffer+4); lastChunk=*((uint32_t*)messageBuffer+5); - compressed=*((uint32_t*)messageBuffer+6); + compressed_size=*((uint32_t*)messageBuffer+6); + selectionTotalSize=*((uint32_t*)messageBuffer+7); + //if we are supporting selection on demand, check if it's not selection notify event + if(serverSupportsExtSelection() && firstChunk && lastChunk && (selectionSize == 0 ) && (selectionTotalSize == 0)) + { + //set input selection with size 0 and notify true. Clipboard will know that we have a notification +#ifdef Q_OS_LINUX + clipboard->setInputSelectionData(selectionClipboard, selectionFormat, true, true, 0, 0, 0, true); +#else + setInputSelectionData(selectionClipboard, selectionFormat, true, true, 0, 0, 0, true); +#endif + } + else if(serverSupportsExtSelection() && lastChunk && (selectionSize==0)) + { + //it's last chunk of incr selection with size 0 +#ifdef Q_OS_LINUX + clipboard->setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, 0, 0, 0); +#else + setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, 0, 0, 0); +#endif + } } else { //if server doesn't support extended selection it'll send data in one chunk, uncompressed firstChunk=true; lastChunk=true; - compressed=false; + compressed_size=0; } currentDataType=SELECTIONBUFFER; - bytesLeftToRead=selectionSize; - qDebug()<<"Get Selection, is Clipboard"<<selectionClipboard<<selectionFormat<<selectionSize; + //if data is compressed, read the "compressed_size" bytes + if(compressed_size) + bytesLeftToRead=compressed_size; + else + bytesLeftToRead=selectionSize; + // qDebug()<<"Get Selection, is Clipboard"<<selectionClipboard<<selectionFormat<<"chunk size"<<selectionSize<<"left"<<bytesLeftToRead; freeMessageBuffer(); } @@ -1085,7 +1158,7 @@ void Client::readDataHeader() } case SELECTION: { - qDebug()<<"Get Selection"; +// qDebug()<<"Get Selection"; getSelection(); break; } @@ -1094,6 +1167,11 @@ void Client::readDataHeader() getServerversion(); break; } + case DEMANDCLIENTSELECTION: + { + getClientSelection(); + break; + } default: { qDebug()<<"Unsupported header type: "<<data_type; @@ -1364,7 +1442,7 @@ void Client::sendClientVersion() version=FEATURE_VERSION; os=OS_LINUX; #ifdef Q_OS_WIN - os=OS_WIN; + os=OS_WINDOWS; #endif #ifdef Q_OS_DARWIN os=OS_DARWIN @@ -1377,6 +1455,19 @@ void Client::sendClientVersion() sendEvent(evmsg); } +//requesting on demand selection +void Client::requestSelectionFromServer(SelectionType sel) +{ + //sending the feature vesrion and OS version to the server + char evmsg[EVLENGTH]{}; + uint16_t selection=sel; + uint32_t etype; + etype=DEMANDSELECTION; + memcpy(evmsg,(char*)&etype,4); + memcpy(evmsg+4,(char*)&selection,2); + sendEvent(evmsg); +} + void Client::geometryChanged() { @@ -1467,6 +1558,19 @@ void Client::setUseRandr(bool use) } } +void Client::send_selnotify_to_server(SelectionType selection, SelectionMime mime) +{ + OutputChunk* chunk=new OutputChunk(selection, mime); + chunk->firstChunk=chunk->lastChunk=true; + + //attach empty chunk to the end of output chunk queue + addToSelectionOutput(chunk); + //send this chunk + sendOutputSelChunk(); + +} + + #ifndef Q_OS_LINUX void Client::slotSelectionChanged(QClipboard::Mode mode) { @@ -1481,42 +1585,78 @@ void Client::slotSelectionChanged(QClipboard::Mode mode) const QMimeData *mimeData = clipboard->mimeData(mode); qDebug()<<"selection changed for"<<mode<<mimeData->formats(); - //if server supports ext selection only sending notification that selection is changed and the list of supported mime types - if(serverSupportsExtSelection()) + SelectionType destination=PRIMARY; + if(mode== QClipboard::Clipboard) + destination=CLIPBOARD; + + SelectionMime mime; + + + if(mimeData->hasImage()) + { + qDebug()<<"Have new Image"; + mime=PIXMAP; + } + else if(mimeData->hasText()) + { + qDebug()<<"Have new Text"; + mime=UTF_STRING; + } + else { + qDebug()<<"Unsupported MIME type in clipboard"; + return; + } + if(serverSupportsExtSelection()) + { + //send notify to server + send_selnotify_to_server(destination,mime); } else { - //server doesn't support ext selection we are sending clipboard data + //send data to server + sendSelectionToServer(destination); } - QByteArray data; - //add size/mime/type of buffer and start copy data - char evmsg[EVLENGTH]{}; +} - uint32_t etype=SELECTIONEVENT; - uint32_t size; - uint8_t destination=PRIMARY; - if(mode== QClipboard::Clipboard) - destination=CLIPBOARD; +void Client::sendSelectionToServer(SelectionType selection) +{ + //sending selection data to server + if(!connected) + return; + const QClipboard *clipboard = QGuiApplication::clipboard(); + + QClipboard::Mode mode; + if(selection==PRIMARY) + mode= QClipboard::Selection; + else + mode =QClipboard::Clipboard; + + if(mode == QClipboard::Clipboard && clipboard->ownsClipboard()) + return; + if(mode == QClipboard::Selection && clipboard->ownsSelection()) + return; - uint8_t mime; + const QMimeData *mimeData = clipboard->mimeData(mode); + SelectionMime mime; + QByteArray data; if(mimeData->hasImage()) { QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); QPixmap pix=clipboard->pixmap(mode); - pix.save(&buffer, "JPG"); - qDebug()<<"Have new Image:"<<pix<<data.size(); + pix.save(&buffer, "PNG"); + qDebug()<<"Selection image size"<<pix<<data.size(); mime=PIXMAP; } else if(mimeData->hasText()) { data=clipboard->text(mode).toUtf8(); - qDebug()<<"Have new Text:"<<data.size(); + qDebug()<<"Selection Text"<<data.size(); mime=UTF_STRING; } else @@ -1524,30 +1664,23 @@ void Client::slotSelectionChanged(QClipboard::Mode mode) qDebug()<<"Unsupported MIME type in clipboard"; return; } - size=data.size(); - if(!size) + + if(!data.size()) { - qDebug()<<"Clipboard has no data"; + qDebug()<<"no data"; return; } - memcpy(evmsg,(char*)&etype,4); - memcpy(evmsg+4,(char*)&size,4); - memcpy(evmsg+8,(char*)&destination,1); - memcpy(evmsg+9,(char*)&mime,1); - - qDebug()<<"SEND SELECTION"<<size<<destination<<mime; - uint32_t sentData=(size < EVLENGTH-10)?size:EVLENGTH-10; - memcpy(evmsg+10,data.data(),sentData); - sendEvent(evmsg); - while(sentData<size) - { - int chunk=(size-sentData < EVLENGTH)?size-sentData:EVLENGTH; - memcpy(evmsg, data.data()+sentData, chunk); - sentData+=chunk; - sendEvent(evmsg); - } - qDebug()<<"sent: "<<sentData<<"from"<<size; + OutputChunk* chunk; + chunk=new OutputChunk(selection, mime); + chunk->totalSize=data.size(); + chunk->data=data; + chunk->mimeData=mime; + chunk->selection=selection; + chunk->firstChunk=true; + chunk->lastChunk=true; + addToSelectionOutput(chunk); + sendOutputSelChunk(); } #endif @@ -1558,7 +1691,7 @@ void Client::sendOutputSelChunk() if(outputSelectionQueue.isEmpty()) return; - OutputChunk* chunk=outputSelectionQueue.takeLast(); + OutputChunk* chunk=outputSelectionQueue.takeFirst(); if(!serverSupportsExtSelection() && (!chunk->firstChunk || !chunk->lastChunk)) { //selection has multiply chunks, but this server doesn't support ext selection, not sending anything @@ -1574,7 +1707,7 @@ void Client::sendOutputSelChunk() uint8_t mime=chunk->mimeData; uint8_t firstChunk=chunk->firstChunk; uint8_t lastChunk=chunk->lastChunk; - uint8_t compressed=chunk->compressed; + uint32_t compressed_size=0; uint32_t totalSize=chunk->totalSize; size=chunk->data.size(); @@ -1587,24 +1720,41 @@ void Client::sendOutputSelChunk() //if server supports extended selection, sending extended header if(serverSupportsExtSelection()) { + //if server supports it compress the big string data + if(chunk->mimeData==UTF_STRING && serverSupportsExtSelection() && size >1024) + { + chunk->data=qCompress(chunk->data); + //Qt puting uncompressed size of data in the first 4 bytes of buffer, we won't send them + compressed_size=chunk->data.size()-4; + } memcpy(evmsg+10,(char*)&firstChunk,1); memcpy(evmsg+11,(char*)&lastChunk,1); - memcpy(evmsg+12,(char*)&compressed,1); - memcpy(evmsg+13,(char*)&totalSize,4); + memcpy(evmsg+12,(char*)&compressed_size,4); + memcpy(evmsg+16,(char*)&totalSize,4); +// qDebug()<<"size of chunk: "<<size<<" compressed: "<<compressed_size<<"total: "<<totalSize; } uint headerSize=10; if(serverSupportsExtSelection()) - headerSize=17; + headerSize=20; // qDebug()<<"SEND SELECTION"<<size<<destination<<mime; + char* data_ptr=chunk->data.data(); + + if(compressed_size) + { + //sending data compressed + data_ptr+=4;//don't send first 4 bytes + size=compressed_size; + } + uint32_t sentData=(size < EVLENGTH-headerSize)?size:EVLENGTH-headerSize; - memcpy(evmsg+headerSize,chunk->data.data(),sentData); + memcpy(evmsg+headerSize,data_ptr,sentData); sendEvent(evmsg); while(sentData<size) { int msg_length=(size-sentData < EVLENGTH)?size-sentData:EVLENGTH; - memcpy(evmsg, chunk->data.data()+sentData, msg_length); + memcpy(evmsg, data_ptr+sentData, msg_length); sentData+=msg_length; sendEvent(evmsg); } @@ -1616,3 +1766,12 @@ void Client::addToSelectionOutput(OutputChunk* chunk) { outputSelectionQueue.append(chunk); } + +int Client::max_chunk() +{ + if(serverSupportsExtSelection()) + { + return 1024*256/4; //256KB + } + return 10*1024*1024/4; //10MB +} diff --git a/client.h b/client.h index 52d1cbf..a947653 100644 --- a/client.h +++ b/client.h @@ -24,8 +24,8 @@ //FEATURE_VERSION is not cooresponding to actual version of client //it used to tell server which features are supported by client -//Changes 0 - 1: sending and recieving client and OS version -#define FEATURE_VERSION 1 +//Changes 1 - 2: supporting extended selection and sending selection on demand +#define FEATURE_VERSION 2 //Version of client OS for same reason enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; @@ -58,6 +58,7 @@ enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; #define UPDATE 8 #define SELECTIONEVENT 9 #define CLIENTVERSION 10 +#define DEMANDSELECTION 11 #define ShiftMask (1<<0) #define LockMask (1<<1) @@ -134,10 +135,9 @@ public: OutputChunk(SelectionType selection, SelectionMime mimeType); QByteArray data; enum SelectionMime mimeData; - bool compressed; - bool firstChunk; - bool lastChunk; - uint totalSize; + bool firstChunk=false; + bool lastChunk=false; + uint32_t totalSize=0; enum SelectionType selection; }; @@ -167,6 +167,11 @@ public: void addToSelectionOutput(OutputChunk* chunk); bool serverSupportsExtSelection(){return serverExtSelection;} ClipboardMode clipboardMode(){return clipMode;} + void requestSelectionFromServer(SelectionType sel); + void send_selnotify_to_server(SelectionType selection, SelectionMime mime); + int max_chunk(); + +public slots: void sendOutputSelChunk(); @@ -203,9 +208,10 @@ public slots: private: enum{ HEADER, FRAMEREGION, REGIONDATA ,CURSORDATA, CURSORLIST, FRAMELIST, SELECTIONBUFFER } currentDataType; - enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION, SERVER_VERSION}; + enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION, SERVER_VERSION, DEMANDCLIENTSELECTION}; void getServerversion(); + void getClientSelection(); void setUseRandr(bool use); void exitOnError(const QString& message); void getImageFrame(); @@ -226,6 +232,10 @@ private: void sendGeometryEvent(); void setFS(int screenNumber); bool wantRepaint=false; +#ifndef Q_OS_LINUX + void sendSelectionToServer(SelectionType selection); + void setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify=false); +#endif //initial values @@ -233,7 +243,7 @@ private: int height=600; //feature version of server - u_int16_t serverVersion=0; + quint16 serverVersion=0; bool fullscreen=false; bool multidisp=false; @@ -273,6 +283,8 @@ private: //input selection chunk variables //size of current chunk uint32_t selectionSize; + //size of complete selection + uint32_t selectionTotalSize; //format of chunk, string or pix SelectionMime selectionFormat; //if true clipboard else primary @@ -281,8 +293,8 @@ private: bool firstChunk; //if it's the last chunk in multiply sel bool lastChunk; - //if the chunk compressed - bool compressed; + //if the chunk compressed the size is > 0 + uint32_t compressed_size; ////////// ClipboardMode clipMode=CLIP_BOTH; //clipboard mode: both, server, client or none @@ -306,8 +318,13 @@ private: ScreenIdentifier *screenIdentifier=0l; QLabel* fr; + //selection #ifdef Q_OS_LINUX XCBClip* clipboard; +#else + SelectionMime selMime; //mime of selection (string or image) + QByteArray selData; //data + uint32_t total_compressed=0; #endif protected: diff --git a/debian/changelog b/debian/changelog index 84f52c2..9f8af45 100644 --- a/debian/changelog +++ b/debian/changelog @@ -25,5 +25,6 @@ x2gokdriveclient (0.0.0.1-0x2go1) UNRELEASED; urgency=medium - add command line argument --selection (both|none|server|client) to specify selection mode. - send a recive feature versions. - give server some time for initialization before sending version. + - support sending and recieving selections on demand. Support reading and writing INCR properties. -- Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Tue, 04 Jun 2019 11:10:43 +0200 diff --git a/xcbclip.cpp b/xcbclip.cpp index e62d311..34cf6c2 100644 --- a/xcbclip.cpp +++ b/xcbclip.cpp @@ -23,8 +23,12 @@ #include <QX11Info> #include <QTimer> #include <QBuffer> +#include <QDateTime> +#define SELECTION_DELAY 30000 //timeout for selection operation +#define INCR_SIZE 256*1024 //size of part for incr selection incr selection + XCBClip::XCBClip(Client* parent) { @@ -46,6 +50,8 @@ XCBClip::XCBClip(Client* parent) values[0] = screen->white_pixel; values[1] = XCB_EVENT_MASK_PROPERTY_CHANGE; + ATOM_CLIPBOARD=atom("CLIPBOARD"); + //create window which will recieve selection events and provide remote selection to X-clients xcb_create_window (con, XCB_COPY_FROM_PARENT, @@ -76,7 +82,7 @@ XCBClip::XCBClip(Client* parent) //we'll recieve sel owner events for primary amd clipboard mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER; xcb_xfixes_select_selection_input_checked(con,clipWinId, XCB_ATOM_PRIMARY, mask); - xcb_xfixes_select_selection_input_checked(con, clipWinId, atom("CLIPBOARD"), mask); + xcb_xfixes_select_selection_input_checked(con, clipWinId, ATOM_CLIPBOARD, mask); } free(xfixes_query); } @@ -84,6 +90,18 @@ XCBClip::XCBClip(Client* parent) QTimer::singleShot(250, this, SLOT(checkEvents())); } +XCBClip::~XCBClip() +{ + for(uint i = delayedSelectionRequests.length()-1; i<=0;--i) + { + discardDelayedRequest(i); + } + //remove all pending INCR requests + remove_obsolete_incr_transactions(false); + xcb_destroy_window(con, clipWinId); + xcb_disconnect(con); +} + xcb_atom_t XCBClip::atom(const QString& name) { @@ -137,6 +155,7 @@ QString XCBClip::atom_name(xcb_atom_t xatom) xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(con, xatom); xcb_get_atom_name_reply_t *reply=xcb_get_atom_name_reply(con, cookie, NULL); + if(!reply) return name; if(!reply->name_len) @@ -228,8 +247,88 @@ xcb_atom_t XCBClip::target_has_atom(const QStringList& atoms, const QString& nam return 0; } +void XCBClip::discardDelayedRequest(uint index) +{ + DelayedRequest *d=delayedSelectionRequests.takeAt(index); + xcb_send_event(con, false, d->request->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)d->event); + xcb_flush(con); + free(d->event); + free((xcb_generic_event_t *)(d->request)); + delete d; +} + + +void XCBClip::processDelayedRequests() +{ + //process delayed requests + for(uint i = delayedSelectionRequests.length()-1; i<=0;--i) + { + DelayedRequest* d=delayedSelectionRequests[i]; + SelectionType selection = selection_from_atom( d->request->selection); + if(currentXTime() > (d->request->time + SELECTION_DELAY)) + { +// qDebug()<<"timeout selection: "<<selection; + discardDelayedRequest(i); + continue; + } + if(!inputSelection[selection].owner) + { +// qDebug()<<"we are not owner of requested selection: "<<selection; + //we are not anymore owners of this selection + discardDelayedRequest(i); + continue; + } + if(inputSelection[selection].timestamp > d->request->time ) + { +// qDebug()<<"selection request for "<<selection<<" is too old"; + //requested selection is older than the current one + discardDelayedRequest(i); + continue; + } + if(!check_req_sanity(d->request)) + { +// qDebug()<<"can't convert selection "<<selection<<" to requested myme type "<<d->request->property; + //our selection don't support requested mime type + discardDelayedRequest(i); + continue; + } + if(inputSelection[selection].state != InputSelection::COMPLETED) + { + //we don't have the data yet + continue; + } + d->event->property=send_data(d->request); + discardDelayedRequest(i); + } +} + + +void XCBClip::updateCurrentTime(xcb_timestamp_t t) +{ + //updating the current X time. It's not very precicely, but enough for us + if(t > lastXTime) + { + //update current time + lastXTime=t; + timeDifference=QDateTime::currentMSecsSinceEpoch() - t; +// qDebug()<<"X time dif:"<<QDateTime::currentMSecsSinceEpoch() - t<<"x11 time"<<t<<"calculated: "<<currentXTime(); + } +} + +xcb_timestamp_t XCBClip::currentXTime() +{ + //get current X time + return QDateTime::currentMSecsSinceEpoch() - timeDifference; + +} + + void XCBClip::checkEvents() { + //check delayed events + processDelayedRequests(); + //delete obsolete INCR transactions if we have something + remove_obsolete_incr_transactions(); xcb_generic_event_t *e=xcb_poll_for_event(con); if(!e) { @@ -244,6 +343,9 @@ void XCBClip::checkEvents() if (response_type == reply->first_event + XCB_XFIXES_SELECTION_NOTIFY) { xcb_xfixes_selection_notify_event_t *notify_event=(xcb_xfixes_selection_notify_event_t *)e; + updateCurrentTime(notify_event->timestamp); + + // qDebug()<<"SEL OWNER notify, selection:"<<notify_event->selection<< " window "<< notify_event->window<< "owner"<< notify_event->owner; if(notify_event->owner == clipWinId) { @@ -265,11 +367,11 @@ void XCBClip::checkEvents() incrAtom=0; if(notify_event->selection==XCB_ATOM_PRIMARY) { - owner[0]=false; + inputSelection[PRIMARY].owner=false; } else { - owner[1]=false; + inputSelection[CLIPBOARD].owner=false; } //get supported mime types request_selection_data( notify_event->selection, atom( "TARGETS"), atom( "TARGETS"), 0); @@ -289,7 +391,13 @@ void XCBClip::checkEvents() } else if (response_type == XCB_SELECTION_REQUEST) { - process_selection_request(e); + if(!process_selection_request(e)) + { + //we delayed processing of this request till data received from server + //we will free the event when we have the data + QTimer::singleShot(10, this, SLOT(checkEvents())); + return; + } } else { @@ -312,6 +420,9 @@ void XCBClip::process_selection_notify(xcb_generic_event_t *e) // qDebug()<<"selection notify"; sel_event=(xcb_selection_notify_event_t *)e; + updateCurrentTime(sel_event->time); + + //processing the event which is reply for convert selection call @@ -326,7 +437,7 @@ void XCBClip::process_selection_notify(xcb_generic_event_t *e) // qDebug()<<"selection notify sel , target , property "<< sel_event->selection<< sel_event->target<< sel_event->property; if(sel_event->property==XCB_NONE) { - qDebug()<<( "NO SELECTION"); +// qDebug()<<( "NO SELECTION"); } else { @@ -338,6 +449,70 @@ void XCBClip::process_selection_notify(xcb_generic_event_t *e) } } +void XCBClip::destroy_incr_transaction(int index) +{ + //destroy incr transaction with index + IncrTransaction* tr=incrTransactions.takeAt(index); + const quint32 mask[] = { XCB_EVENT_MASK_NO_EVENT }; + //don't resive property notify events for this window anymore + xcb_change_window_attributes(con, tr->requestor, + XCB_CW_EVENT_MASK, mask); + xcb_flush(con); + + delete tr; +} + + +void XCBClip::remove_obsolete_incr_transactions( bool checkTs) +{ + //remove_obsolete_incr_transactions + //if checkTS true, check timestamp and destroy only if ts exceed delay + for (int i= incrTransactions.size()-1;i>=0;--i) + { + if( (!checkTs) || ( incrTransactions[i]->timestamp+SELECTION_DELAY < QDateTime::currentMSecsSinceEpoch())) + { + qDebug()<<"timeout INCR selection for "<<incrTransactions[i]->requestor<<incrTransactions[i]->timestamp<< QDateTime::currentMSecsSinceEpoch(); + destroy_incr_transaction(i); + } + } +} + + +void XCBClip::process_incr_transaction_property(xcb_property_notify_event_t * pn) +{ + //process incr transactions + for (int i=0;i < incrTransactions.size();++i) + { + IncrTransaction* tr=incrTransactions[i]; + if((tr->requestor == pn->window) && (tr->property == pn->atom ) && ( pn->state == XCB_PROPERTY_DELETE) ) + { + //requestor ready for the new portion of data + uint left=tr->data.size()-tr->sentBytes; + if(!left) + { +// qDebug()<<"all INCR data sent to"<<tr->requestor; + //all data sent, sending NULL data and destroying transaction + xcb_change_property(con, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property, + tr->target, 8, 0, NULL); + xcb_flush(con); + destroy_incr_transaction(i); + return; + } + uint sendingBytes=(INCR_SIZE< left)?INCR_SIZE:left; + +// qDebug()<<"sending incr bytes"<<sendingBytes ; + + xcb_change_property(con, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property, + tr->target, 8, sendingBytes, tr->data.constData() + tr->sentBytes); + xcb_flush(con); + tr->sentBytes+=sendingBytes; + tr->timestamp=QDateTime::currentMSecsSinceEpoch(); + return; + } + } + //notify event doesn't belong to any of started incr transactions or it's notification for new property + return; +} void XCBClip::process_property_notify(xcb_generic_event_t *e) { @@ -346,9 +521,13 @@ void XCBClip::process_property_notify(xcb_generic_event_t *e) // qDebug()<<("property notify"); pn = (xcb_property_notify_event_t *)e; + updateCurrentTime(pn->time); + if (pn->window != clipWinId) { -// qDebug()<<("not our window"); + //this property doesn't belong to our window; + //let's check if it's not the property corresponding to one of incr transactions + process_incr_transaction_property(pn); return; } // qDebug()<<"property, state "<< pn->atom<< pn->state; @@ -375,7 +554,7 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) //request property which represents value of selection (data or mime types) //get max 100K of data, we don't need to send more than that over network for perfomance reasons - cookie= xcb_get_property(con,false, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, 0, max_chunk()); + cookie= xcb_get_property(con,false, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, 0, parent->max_chunk()); reply=xcb_get_property_reply(con, cookie, NULL); if(!reply) { @@ -385,7 +564,7 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) { if(reply->type==XCB_NONE) { - qDebug()<<( "NONE reply"); +// qDebug()<<( "NONE reply"); } else { @@ -420,17 +599,27 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) { QStringList atoms=atomsInReply(reply); // qDebug() << "target supports mime types:"<<atoms; - + data_atom=0; + //get the best of supported mime types and request the selection in this format + data_atom=best_atom_from_list(atoms); if(parent->serverSupportsExtSelection()) { //servere support extended selection, we'll send the selection data first when it's requested by the server //now we'll just notify the server that there is the new selection and send supported mime types + SelectionType sel=selection_from_atom(selection); + best_atom[sel]=data_atom; + SelectionMime mime=UTF_STRING; + + if( ! is_string_atom(best_atom[sel]) ) + { + mime = PIXMAP; + } + +// qDebug()<<"MIME"<<mime; + parent->send_selnotify_to_server(sel,mime); } else { - data_atom=0; - //get the best of supported mime types and request the selection in this format - data_atom=best_atom_from_list(atoms); xcb_delete_property( con, clipWinId, property); xcb_flush(con); @@ -440,7 +629,7 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) request_selection_data( selection, data_atom, data_atom, 0); else { - qDebug()<<( "there are no supported mime types in the target"); +// qDebug()<<( "there are no supported mime types in the target"); } } } @@ -462,9 +651,7 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) - SelectionType sel= CLIPBOARD; - if(selection==XCB_ATOM_PRIMARY) - sel=PRIMARY; + SelectionType sel= selection_from_atom(selection); SelectionMime mime= UTF_STRING; if(is_image_atom(reply->type)) @@ -478,18 +665,14 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) chunk->data.setRawData((const char*) xcb_get_property_value(reply),xcb_get_property_value_length(reply)); } - chunk->compressed=false; if(is_string_atom(property)) + { chunk->mimeData=UTF_STRING; + } else chunk->mimeData=PIXMAP; - if(selection == XCB_ATOM_PRIMARY) - { - chunk->selection=PRIMARY; - } - else - chunk->selection=CLIPBOARD; + chunk->selection=selection_from_atom(selection); if(incrementalSize && (incrAtom==property)) @@ -540,7 +723,7 @@ void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property) if(bytes_left) { free(reply); - cookie= xcb_get_property(con, 0, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, bytes_read/4,max_chunk()); + cookie= xcb_get_property(con, 0, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, bytes_read/4,parent->max_chunk()); reply=xcb_get_property_reply(con, cookie, NULL); if(!reply) { @@ -575,6 +758,21 @@ SelectionType XCBClip::selection_from_atom(xcb_atom_t selection) } +xcb_atom_t XCBClip::atom_from_selection(SelectionType selection) +{ + if(selection == PRIMARY) + return XCB_ATOM_PRIMARY; + return ATOM_CLIPBOARD; + +} + +void XCBClip::requestSelectionData(SelectionType selection) +{ + //Client requesting data for selection using this function + request_selection_data(atom_from_selection(selection), best_atom[selection], best_atom[selection], 0); +} + + void XCBClip::request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t) { //execute convert selection for primary or clipboard to get mimetypes or data (depends on target atom) @@ -589,23 +787,53 @@ void XCBClip::request_selection_data( xcb_atom_t selection, xcb_atom_t target, } -void XCBClip::setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, bool compressed, uint size, char* data) +void XCBClip::setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify) { + //if notify is true, we don't have actual data, just notification //copy data to selection buffer -// qDebug()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size; +// qDebug()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size<<notify; if(firstChunk) { - selData[selection].clear(); - selMime[selection]=mime; + inputSelection[selection].selData.clear(); + inputSelection[selection].selMime=mime; + total_compressed=0; } - selData[selection].append(data,size); + if(!compressed) + inputSelection[selection].selData.append(data,size); + else + { + QByteArray ba; + ba.append((char*) &size, 4); + ba.append(data, compressed); + + total_compressed+=compressed; + + inputSelection[selection].selData.append(qUncompress(ba)); +// qDebug()<<"uncompress from "<<compressed<<" to "<<size; + } - if(lastChunk) + if(lastChunk ) { - own_selection(selection); + if(notify) + { + inputSelection[selection].state=InputSelection::NOTIFIED; +// qDebug()<<"Got selection notify from server"; + own_selection(selection); + } + else + { + //if state is requested, means we already own a selection + if(inputSelection[selection].state!=InputSelection::REQUESTED) + { + own_selection(selection); + } + inputSelection[selection].state=InputSelection::COMPLETED; +// qDebug()<<"Got selection data for "<<selection<<"total size: "<<inputSelection[selection].selData.size()<<"compressed size"<<total_compressed; + } + } } @@ -620,21 +848,25 @@ void XCBClip::own_selection(SelectionType selection) default: break; } - xcb_atom_t sel=XCB_ATOM_PRIMARY; - if(selection!=PRIMARY) - { - sel=atom("CLIPBOARD"); - } + xcb_atom_t sel=atom_from_selection(selection); xcb_set_selection_owner(con, clipWinId, sel, XCB_CURRENT_TIME); xcb_flush(con); - owner[selection]=true; - timestamp[selection]=XCB_CURRENT_TIME; + inputSelection[selection].owner=true; + inputSelection[selection].timestamp=currentXTime(); } -void XCBClip::process_selection_request(xcb_generic_event_t *e) +bool XCBClip::process_selection_request(xcb_generic_event_t *e) { + //processing selection request. + //return true if the processing is finishing after return + //false if data is not ready and we are delaying processing of this request + //in this case calling function SHOULD NOT destroy the request neither event should not be destroyed + //we'll free this objects after processing of the request when the data is available + xcb_selection_request_event_t *req=(xcb_selection_request_event_t*)e; + updateCurrentTime(req->time); + xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1); event->response_type = XCB_SELECTION_NOTIFY; event->requestor = req->requestor; @@ -651,25 +883,25 @@ void XCBClip::process_selection_request(xcb_generic_event_t *e) SelectionType sel=selection_from_atom(req->selection); -// qDebug()<<"selection request for"<<atom_name(req->selection)<<atom_name(req->target)<<atom_name(req->property); - if(!owner[sel]) +// qDebug()<<"selection request for"<<atom_name(req->selection)<<atom_name(req->target)<<atom_name(req->property)<< "from "<<req->requestor<<"we are "<<clipWinId; + if(!inputSelection[sel].owner) { //we don't own this selection - qDebug()<<"not our selection"; +// qDebug()<<"not our selection"; xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); xcb_flush(con); free(event); - return; + return true; } - if(timestamp[sel] > req->time) + if(inputSelection[sel].timestamp > req->time) { //selection changed after request - qDebug()<<"requested selection doesn't exist anymore"; +// qDebug()<<"requested selection doesn't exist anymore"; xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); xcb_flush(con); free(event); - return; + return true; } @@ -678,7 +910,7 @@ void XCBClip::process_selection_request(xcb_generic_event_t *e) event->property=property; // qDebug()<<"requested TIMESTAMP"; xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, - property, XCB_ATOM_INTEGER, 32, 1, ×tamp[sel]); + property, XCB_ATOM_INTEGER, 32, 1, &inputSelection[sel].timestamp); } else if(req->target==atom("TARGETS")) @@ -689,11 +921,39 @@ void XCBClip::process_selection_request(xcb_generic_event_t *e) } else { - event->property=send_data(req); + if(check_req_sanity(req)) + { + //if data is ready, send it to requestor + if(inputSelection[sel].state == InputSelection::COMPLETED) + event->property=send_data(req); + else + { + //if data is not ready, request it from server and delay the processing of request + delay_selection_request(req, event); + return false; + } + } } xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); xcb_flush(con); free(event); + return true; +} + +void XCBClip::delay_selection_request( xcb_selection_request_event_t *request, xcb_selection_notify_event_t* event) +{ + SelectionType sel=selection_from_atom(request->selection); + DelayedRequest* dr=new DelayedRequest; + dr->event=event; + dr->request=request; + //add new request to the queue + delayedSelectionRequests<<dr; + if(inputSelection[sel].state==InputSelection::NOTIFIED) + { + //if we didn't request the data yet, let's do it now + parent->requestSelectionFromServer(sel); + inputSelection[sel].state=InputSelection::REQUESTED; + } } QString XCBClip::mime_to_QT_img(const QString& mimeType) @@ -710,54 +970,91 @@ QString XCBClip::mime_to_QT_img(const QString& mimeType) xcb_atom_t XCBClip::set_data_property(xcb_selection_request_event_t* req, QByteArray* data) { - //set data to window property //change when implemented - bool support_incr=false; + bool support_incr=true; //this types of application not supporting incr selection if(atom_name(req->property)=="_XT_SELECTION_0" || atom_name(req->property)=="_QT_SELECTION") { - qDebug()<<atom_name(req->property)<<"doesn't support INCR"; +// qDebug()<<atom_name(req->property)<<"doesn't support INCR"; support_incr=false; } -// qDebug()<<"check if data size < "<<xcb_get_maximum_request_length(con) * 4 - 24; //check if we are sending incr - if(data->size() < (int)xcb_get_maximum_request_length(con) * 4 - 24) + + if(!support_incr) { -// qDebug()<<"sending "<<data->size()<<atom_name(req->property)<<atom_name(req->target); + if( data->size() < (int)xcb_get_maximum_request_length(con) * 4 - 24) + { + //requester doesn't support INCR, sending data in one chunk + xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target, + 8, data->size(), (const void *)data->constData()); + + xcb_flush(con); + return req->property; + } + qDebug()<<"data is too big"; + return XCB_NONE; + } + if(data->size() < INCR_SIZE) + { + //if size is < 256K send in one property xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target, 8, data->size(), (const void *)data->constData()); - xcb_flush(con); return req->property; } - qDebug()<<"data is too big"; - return XCB_NONE; + //sending INCR atom to let requester know that we are starting data incrementally + uint bytes=data->size(); + qDebug()<<"starting INCR send of size"<<data->size()<<" for win ID "<<req->requestor; + xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, + atom("INCR"), 32, 1, (const void *)&bytes); + startIncrTransaction(req->requestor, req->property, req->target, *data ); + xcb_flush(con); + + return req->property; } -xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req) + +//creating INCR transaction +void XCBClip::startIncrTransaction(xcb_window_t requestor, xcb_atom_t property, xcb_atom_t target, QByteArray data) { + IncrTransaction* tr=new IncrTransaction; + tr->requestor=requestor; + tr->property=property; + tr->target=target; + tr->data=data; + tr->sentBytes=0; + tr->timestamp=QDateTime::currentMSecsSinceEpoch(); + incrTransactions<<tr; + qDebug()<<"INCR start"<<tr->timestamp; + const quint32 mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + //we'll recive property change events for requestor window from now + xcb_change_window_attributes(con, requestor, + XCB_CW_EVENT_MASK, mask); +} - //send data - SelectionType sel=selection_from_atom(req->selection); - if(selMime[sel]==UTF_STRING) +//check if the requested mime can be delivered +bool XCBClip::check_req_sanity(xcb_selection_request_event_t* req) +{ + SelectionType sel=selection_from_atom(req->selection); + if(inputSelection[sel].selMime==UTF_STRING) { //if it's one of supported text formats send without convertion if(is_string_atom(req->target)) { -// qDebug()<<"sending UTF text"; - return set_data_property(req, &selData[sel]); + // qDebug()<<"sending UTF text"; + return true; } else { qDebug()<<"unsupported property requested:"<<atom_name(req->target); - return XCB_NONE; + return false; } } else @@ -765,11 +1062,28 @@ xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req) if(!is_image_atom(req->target)) { qDebug()<<"unsupported property requested:"<<atom_name(req->target); - return XCB_NONE; + return false; } + // qDebug()<<"sending "<<atom_name(req->target); + return true; + } +} + + +xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req) +{ + //send data + SelectionType sel=selection_from_atom(req->selection); + if(inputSelection[sel].selMime==UTF_STRING) + { + //if it's one of supported text formats send without convertion + return set_data_property(req, &inputSelection[sel].selData); + } + else + { // qDebug()<<"sending "<<atom_name(req->target); //convert to desireable format - QImage img=QImage::fromData(selData[sel]); + QImage img=QImage::fromData(inputSelection[sel].selData); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); @@ -777,7 +1091,6 @@ xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req) // qDebug()<<"converted to"<<mime_to_QT_img(atom_name(req->target))<<ba.size(); return set_data_property(req, &ba); } - return XCB_NONE; } void XCBClip::send_mime_types(xcb_selection_request_event_t* req) @@ -792,7 +1105,7 @@ void XCBClip::send_mime_types(xcb_selection_request_event_t* req) if((a=atom("TIMESTAMP"))) targets.append(a); - if(selMime[sel]==PIXMAP) + if(inputSelection[sel].selMime==PIXMAP) { if((a=atom("image/png"))) targets.append(a); @@ -825,10 +1138,4 @@ void XCBClip::send_mime_types(xcb_selection_request_event_t* req) } -uint XCBClip::max_chunk() -{ - if(parent->serverSupportsExtSelection()) - return 1024*100/4; //100KB - else - return 10*1024*1024/4; //10MB -} + diff --git a/xcbclip.h b/xcbclip.h index c45fc91..7787e50 100644 --- a/xcbclip.h +++ b/xcbclip.h @@ -34,17 +34,18 @@ class XCBClip : public QObject Q_OBJECT public: XCBClip(Client* parent); - void setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, bool compressed, uint size, char* data); + ~XCBClip(); + void setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify=false); + void requestSelectionData(SelectionType selection); private: - uint max_chunk(void); xcb_atom_t atom(const QString& name); QString atom_name(xcb_atom_t xatom); QStringList atomsInReply(xcb_get_property_reply_t *reply); xcb_atom_t best_atom_from_list(const QStringList& atoms); void process_selection_notify(xcb_generic_event_t *e); - void process_selection_request(xcb_generic_event_t *e); + bool process_selection_request(xcb_generic_event_t *e); void request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t); void process_property_notify(xcb_generic_event_t *e); void read_selection_property(xcb_atom_t selection, xcb_atom_t property); @@ -56,7 +57,18 @@ private: xcb_atom_t send_data(xcb_selection_request_event_t* req); xcb_atom_t set_data_property(xcb_selection_request_event_t* req, QByteArray* data); SelectionType selection_from_atom(xcb_atom_t selection); + xcb_atom_t atom_from_selection(SelectionType selection); QString mime_to_QT_img(const QString& mimeType); + void delay_selection_request( xcb_selection_request_event_t *reqest, xcb_selection_notify_event_t* event); + bool check_req_sanity(xcb_selection_request_event_t* req); + void processDelayedRequests(); + void discardDelayedRequest(uint index); + void updateCurrentTime(xcb_timestamp_t t); + xcb_timestamp_t currentXTime(); + void startIncrTransaction(xcb_window_t requestor, xcb_atom_t property, xcb_atom_t target, QByteArray data); + void process_incr_transaction_property(xcb_property_notify_event_t * pn); + void destroy_incr_transaction(int index); + void remove_obsolete_incr_transactions(bool checkTS=true); private slots: void checkEvents(); @@ -70,10 +82,43 @@ private: xcb_atom_t incrAtom=0; uint incrementalSize=0; uint incrementalSizeRead=0; + xcb_atom_t best_atom[2]; //the best mime type for selection to request on demand sel request from server + xcb_atom_t ATOM_CLIPBOARD=0; //atom for CLIPBOARD selection + //input selection - SelectionMime selMime[2]; - QByteArray selData[2]; - bool owner[2]={0}; - xcb_timestamp_t timestamp[2]; + + struct InputSelection{ + SelectionMime selMime; //mime of selection (string or image) + QByteArray selData; //data + bool owner=false; //if we are owners of this selction + enum { NOTIFIED, REQUESTED, COMPLETED} state; //state of the selection + xcb_timestamp_t timestamp; //timestamp when we own selection + }inputSelection[2]; + + //requests which processing should be delayed till we get data from server + struct DelayedRequest + { + xcb_selection_request_event_t *request; // request from client + xcb_selection_notify_event_t* event; // event which we are going to send to client + }; + QList <DelayedRequest*> delayedSelectionRequests; + + //save running INCR transactions in this struct + struct IncrTransaction + { + xcb_window_t requestor; + xcb_atom_t property; + xcb_atom_t target; + QByteArray data; + uint sentBytes; + qint64 timestamp; + }; + QList <IncrTransaction*> incrTransactions; + + + xcb_timestamp_t lastXTime=XCB_TIME_CURRENT_TIME; //current time of X Server + int32_t timeDifference=0; //differense between X server time and system time + + uint32_t total_compressed=0; }; #endif -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdriveclient.git