This is an automated email from the git hooks/post-receive script. x2go pushed a change to branch master in repository x2gokdriveclient. from 38bba51 client.cpp: allow building with Qt 5.8-, which does not support QWidget::setWindowFlag (). new 67abd98 support for sending frames over UDP. Some improvements in detecting of unchanged regions. 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 | 513 +++++++++++++++++++++++++++++++++++++++++++++++-------- client.h | 74 +++++++- debian/changelog | 2 + displayarea.cpp | 7 + 4 files changed, 519 insertions(+), 77 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 67abd98aa90cc7fc18ca17e99718b1b3e47c5fce Author: Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> Date: Thu Dec 22 17:33:51 2022 -0600 support for sending frames over UDP. Some improvements in detecting of unchanged regions. --- client.cpp | 513 +++++++++++++++++++++++++++++++++++++++++++++++-------- client.h | 74 +++++++- debian/changelog | 2 + displayarea.cpp | 7 + 4 files changed, 519 insertions(+), 77 deletions(-) diff --git a/client.cpp b/client.cpp index 6f8cf57..cba4fe1 100644 --- a/client.cpp +++ b/client.cpp @@ -19,12 +19,13 @@ */ #include <QTcpSocket> +#include <QUdpSocket> +#include <QTime> #include "client.h" #include "displayarea.h" #include <QApplication> #include <QMessageBox> #include <QTimer> -#include <QTcpSocket> #include <QPainter> #include <QImage> #include <QFile> @@ -65,6 +66,50 @@ //stderr +DgramPacket::DgramPacket(uint16_t seq, uint16_t numOfDatagrams) +{ + datagrams.resize(numOfDatagrams); + packetSeq=seq; +} + +//return vector with numbers of missing datagrams + +//return true if the packet is complete after adding datagram +bool DgramPacket::addDgram(QByteArray dgram) +{ + uint16_t dgSeq=*((uint16_t*)dgram.constData()+4); + //never should happen + if(dgSeq>=datagrams.size()) + { + KDRStdErr()<<"Wrong DGRAM seq number "<<dgSeq<<" from packet "<<*((uint16_t*)data.constData()+2)<<KDR_ENDL; + return false; + } + if(!datagrams[dgSeq].isEmpty()) + { + KDRStdErr()<<"Duplicate DGRAM seq number "<<dgSeq<<" from packet "<<*((uint16_t*)data.constData()+2)<<KDR_ENDL; + return false; + } + datagrams[dgSeq]=dgram; + int dataSize=0; + for(int i=0; i< datagrams.size(); ++i) + { + if(datagrams[i].isEmpty()) + return false; + dataSize+=(datagrams[i].size()-SRVDGRAMHEADERSIZE); + } + //packet is complete + for(int i=0; i< datagrams.size(); ++i) + { + //remove datagram header + datagrams[i].remove(0,SRVDGRAMHEADERSIZE); + data.append(datagrams[i]); + } + //packet is ready + complete=true; + return true; +} + + X2GoCursor::X2GoCursor(uint16_t width, uint16_t height, uint16_t xhot, uint16_t yhot, uint32_t serialNumber, uint32_t dataSize) { this->width=width; @@ -139,7 +184,7 @@ QString Client::QSizeToStr(const QSizeF& sz) return str; } -QTextStream& Client::KDRStdErr(bool dbg) +QTextStream& Client::KDRStdErrFunc(bool dbg) { static QTextStream out(stderr); static QString nls; @@ -147,7 +192,7 @@ QTextStream& Client::KDRStdErr(bool dbg) static QTextStream nl(&nls); if(debug || !dbg) { - out<<KDR_ENDL; +// out<<KDR_ENDL; return out; } return nl; @@ -166,13 +211,15 @@ Client::Client() initDesktopMode(); clientSocket=new QTcpSocket(this); - - - connect(clientSocket, SIGNAL(connected()), this, SLOT(socketConnected())); connect(clientSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect(clientSocket, SIGNAL(readyRead()), this, SLOT(dataArrived()) ); + + + udpSocket=new QUdpSocket(this); + connect(udpSocket, SIGNAL(readyRead()), this, SLOT(UDPDataArrived())); + #ifndef Q_OS_LINUX connect(QGuiApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(slotSelectionChanged(QClipboard::Mode))); #endif @@ -210,10 +257,7 @@ Client::~Client() freeMessageBuffer(); - if(currentFrame) - delete currentFrame; - if(currentCursor) - delete currentCursor; + reinitCaches(); } void Client::initDesktopMode() @@ -348,7 +392,19 @@ void Client::slotEnableRandr() void Client::slotDisconnect() { + if(serverVersion >=8) + {//sending disconnect event to server + char evmsg[EVLENGTH]{}; + uint32_t etype; + etype=DISCONNECTCLIENT; + memcpy(evmsg,(char*)&etype,4); + sendEvent(evmsg); + } + clientSocket->close(); + if(udpSocket->isOpen()) + udpSocket->close(); + } void Client::slotDisplayFS() @@ -569,6 +625,12 @@ void Client::parseOptions() KDRStdErr()<<"Disable resizing"; continue; } + if(args[i]=="--udp-frames") + { + udpFrames=true; + udpHost=args[++i]; + continue; + } if(args[i]=="--connect") { host=args[++i]; @@ -716,7 +778,7 @@ void Client::connectToServer() { hide(); } - KDRStdErr(false)<<"Connecting to remote host "<<host<<":"<<port<<KDR_ENDL; + KDRStdErr(false)<<"Connecting to remote host "<<host<<":"<<port<<" over TCP"<<KDR_ENDL; clientSocket->connectToHost(host, port); } @@ -725,11 +787,15 @@ QPixmap Client::getPixmapFromCache(uint32_t crc) { if(!frameCache.contains(crc)) { - KDRStdErr()<<"GETPIXMAP: frame "<<KDR_HEX<<crc<<" not found in cache"; + KDRStdErr()<<"GETPIXMAP: frame "<<KDR_HEX<<crc<<" not found in cache"<<KDR_ENDL; if(serverVersion<5) - exitOnError(tr("Frame not found in cache")); - else + return QPixmap(); + if(serverVersion<8) + { requestCacheRebuild(); + return QPixmap(); + } + requestFrame(crc); return QPixmap(); } return frameCache[crc]; @@ -751,18 +817,7 @@ void Client::renderFrame() this->resize(currentFrame->width, currentFrame->height); } displayImage=QImage(currentFrame->width, currentFrame->height, QImage::Format_RGB32); -/* dispImagePainter.begin(&displayImage); - dispImagePainter.drawPixmap(0,0,currentFrame->regions[0]->pix); - dispImagePainter.end();*/ -// saveDispImage("/tmp/dispInit.png"); - } -// else -// { -// dispImagePainter.begin(&displayImage); -// dispImagePainter.drawPixmap(currentFrame->x,currentFrame->y,currentFrame->regions[0]->pix); -// dispImagePainter.end(); -// // saveDispImage("/tmp/dispUpdate.png"); -// } + } } else { @@ -771,7 +826,14 @@ void Client::renderFrame() QPainter painter; //We have new Image. We need to create a Pixmap and add to cache QPixmap pix(currentFrame->width, currentFrame->height); - painter.begin(&pix); + if(pix.isNull()) + { + KDRStdErr()<<"Error allocating new pixmap: "<<currentFrame->width<<"x"<<currentFrame->height<<KDR_ENDL; + } + if(!painter.begin(&pix)) + { + KDRStdErr()<<"Error painting new image "<<currentFrame->width<<"x"<<currentFrame->height<<KDR_ENDL; + } for(int i=0;i<currentFrame->regions.size();++i) { FrameRegion* reg=currentFrame->regions[i]; @@ -779,12 +841,12 @@ void Client::renderFrame() { if(!frameCache.contains(reg->source_crc)) { - KDRStdErr()<<"region "<<KDR_HEX<<reg->source_crc<<" not found in cache"; - if(serverVersion<5) - exitOnError(tr("region not found in cache")); - else - requestCacheRebuild(); - return; + KDRStdErr()<<"Region "<<KDR_HEX<<reg->source_crc<<" not found in cache, fill with display image"<<KDR_ENDL; + painter.drawImage(reg->x,reg->y,displayImage, reg->x+currentFrame->x,reg->y+currentFrame->y,reg->width, reg->height); + } + else + { +// KDRStdErr()<<"Got region from cache "<<reg->source_crc<<KDR_ENDL; } //KDRStdErr()<<"REG:"<<reg->x<<reg->y<<reg->width<< reg->height<<"SOURCE:"<<reg->source_x<<reg->source_y; painter.drawPixmap(reg->x,reg->y,reg->width, reg->height, frameCache[reg->source_crc], @@ -798,18 +860,11 @@ void Client::renderFrame() } painter.end(); frameCache.insert(currentFrame->crc, pix); +// KDRStdErr()<<"Add to cache: "<<currentFrame->crc<<KDR_ENDL; int frameSize=pix.width()*pix.height()*pix.depth()/8; cacheSize+=frameSize; frameCount++; -// saveDispImage("/tmp/dispDraw.png"); - /*KDRStdErr()<<"Insert in cache Frame:"<<frameCount<<dec<<frameSize<<pix.size()<< - frameCache.count()<<"Total(MB)"<<cacheSize/(1024*1024);*/ -// QPainter dispImagePainter; -// dispImagePainter.begin(this->getDisplayImage()); -// dispImagePainter.drawPixmap(currentFrame->x,currentFrame->y,pix); -// dispImagePainter.end(); - } } wantRepaint=true; @@ -877,6 +932,71 @@ void Client::setUptodate() +void Client::getImageFrameFromDGPacket(QByteArray data) +{ + uint32_t winId=0; + const char* messageBuffer=data.constData(); + if(*((uint32_t*)messageBuffer) == CACHEFRAME) + { + uint32_t crc=*((uint32_t*)messageBuffer+1); + KDRStdErr()<<"Server resent frame with crc "<<KDR_HEX<<crc<<KDR_ENDL; + QPixmap pix; + if(!pix.loadFromData((const uchar*)(messageBuffer+8),data.length()-8)) + { + KDRStdErr()<<"failed to load pix from data"<<KDR_ENDL; + } + else + { + if(frameCache.contains(crc)) + { + KDRStdErr()<<"Frame is already in cache"<<KDR_ENDL; + } + else + { + frameCache.insert(crc, pix); + } + } + + return; + } + if(currentFrame) + delete currentFrame; + + if(rootless) + winId=*((uint32_t*)messageBuffer+7); + currentFrame=new Frame(*((uint32_t*)messageBuffer+1), *((uint32_t*)messageBuffer+2),*((uint32_t*)messageBuffer+3), + *((uint32_t*)messageBuffer+4), + *((uint32_t*)messageBuffer+5), *((uint32_t*)messageBuffer+6), winId); + messageBuffer+=8*4; + + for(uint i=0;i<currentFrame->numOfRegions;++i) + { + FrameRegion* region=new FrameRegion(*((uint32_t*)messageBuffer), *((uint32_t*)messageBuffer+1),*((uint32_t*)messageBuffer+2), *((uint32_t*)messageBuffer+3), + *((uint32_t*)messageBuffer+4), *((uint32_t*)messageBuffer+5), *((uint32_t*)messageBuffer+6),*((uint32_t*)messageBuffer+7)); + messageBuffer+=8*4; + currentFrame->regions.append(region); + if(!region->source_crc) + { + if(currentFrame->regions.last()->pix.loadFromData((uchar*)messageBuffer, currentFrame->regions.last()->dataSize)) + { + } + else + { + KDRStdErr()<<"=============================Image loading failed: "<<KDR_HEX<<currentFrame->crc<<" Replacing with display image"<<KDR_ENDL; + region->pix=QPixmap(region->width, region->height); + QPainter painter; + painter.begin(®ion->pix); + painter.drawImage(region->x,region->y,displayImage, region->x+currentFrame->x,region->y+currentFrame->y,region->width, region->height); + painter.end(); + + } + messageBuffer+=currentFrame->regions.last()->dataSize; + } + } + renderFrame(); +} + + void Client::getImageFrame() { if(currentFrame) @@ -973,7 +1093,6 @@ void Client::getCursor() if(currentCursor) delete currentCursor; - currentCursor=new X2GoCursor(*((uint16_t*)messageBuffer+5), *((uint16_t*)messageBuffer+6), *((uint16_t*)messageBuffer+7), *((uint16_t*)messageBuffer+8), *((uint32_t*)messageBuffer+5),*((uint32_t*)messageBuffer+6)); @@ -986,7 +1105,7 @@ void Client::getCursor() //we don't have data, set cursor if(!cursorCache.contains(currentCursor->serialNumber)) { - KDRStdErr()<<"cursor not found: "<<currentCursor->serialNumber; + KDRStdErr()<<"cursor not found: "<<currentCursor->serialNumber<<KDR_ENDL; if(serverVersion<5) exitOnError(tr("Cursor not found in cache")); else @@ -1105,7 +1224,6 @@ void Client::setInputSelectionData(SelectionType, SelectionMime mime, bool first } #endif - void Client::getDeletedCursorsList() { //process list from messageBuffer @@ -1115,12 +1233,14 @@ void Client::getDeletedCursorsList() uint32_t serial=*((uint32_t*)messageBuffer+i); if(!cursorCache.contains(serial)) { - KDRStdErr()<<"cursor not found in cache: "<<serial; - if(serverVersion<5) - exitOnError(tr("cursor not found in cache")); - else + KDRStdErr()<<"cursor not found in cache: "<<serial<<KDR_ENDL; + if(serverVersion>5) + { requestCacheRebuild(); - return; + return; + } + else + continue; } delete cursorCache[serial]; cursorCache.remove(serial); @@ -1288,7 +1408,7 @@ void Client::getWinUpdateBuffer() // KDRStdErr()<<"win has parent!!!!!: "<<KDR_HEX<<parId; if(!parentWin) { - KDRStdErr()<<"parent Win not found in list: "<<KDR_HEX<<parId; + KDRStdErr()<<"parent Win not found in list: "<<KDR_HEX<<parId<<KDR_ENDL; parentWin=(ExtWin*) this; } win=new ExtWin(extWinId,this, 0, winType, flags); @@ -1351,7 +1471,7 @@ void Client::getWinUpdateBuffer() { //set new parent and remap window win->setParentId(parId); - KDRStdErr()<<"Reparent window"; + KDRStdErr()<<"Reparent window"<<KDR_ENDL; } if(win->getNextSibId()!=nextSibId) { @@ -1373,19 +1493,14 @@ void Client::getWinUpdateBuffer() void Client::getDeletedFramesList() { //process list from messageBuffer - -// KDRStdErr()<<"get deleted frames: "<<deletedFramesSize; +// KDRStdErr()<<"get deleted frames: "<<deletedFramesSize<<KDR_ENDL; for(uint i=0;i<deletedFramesSize;++i) { uint32_t crc=*((uint32_t*)messageBuffer+i); if(!frameCache.contains(crc)) { - KDRStdErr()<<"DELETING: frame not found in cache: "<<KDR_HEX<<crc; - if(serverVersion<5) - exitOnError(tr("frame not found in cache")); - else - requestCacheRebuild(); - return; +// KDRStdErr()<<"DELETING: frame not found in cache: "<<KDR_HEX<<crc<<KDR_ENDL; + continue; } // KDRStdErr()<<"deleting frame from cache with crc"<<KDR_HEX<<crc; QPixmap pix=frameCache[crc]; @@ -1479,21 +1594,22 @@ void Client::getSelection() compressed_size=0; } - currentDataType=SELECTIONBUFFER; //if data is compressed, read the "compressed_size" bytes if(compressed_size) + { bytesLeftToRead=compressed_size; + } else bytesLeftToRead=selectionSize; - // KDRStdErr()<<"Get Selection, is Clipboard"<<selectionClipboard<<selectionFormat<<"chunk size"<<selectionSize<<"left"<<bytesLeftToRead; +// KDRStdErr()<<"Get Selection, is Clipboard "<<selectionClipboard<<selectionFormat<<" chunk size "<<selectionSize<<" left "<<bytesLeftToRead<<KDR_ENDL; freeMessageBuffer(); } void Client::getDeletedFrames() { - //get list of deleted cursors + //get list of deleted frames bytesReady=0; deletedFramesSize=*((uint32_t*)messageBuffer+1); bytesLeftToRead=deletedFramesSize * sizeof(uint32_t); @@ -1506,7 +1622,6 @@ void Client::getRegionImage() { // KDRStdErr()<<"got image for region "<<currentFrame->regions.count()-1<<"from"<<currentFrame->numOfRegions; -// currentFrame->regions.last()->pix=new QPixmap(); if(currentFrame->regions.last()->pix.loadFromData((uchar*)messageBuffer, currentFrame->regions.last()->dataSize)) { // QString fname; @@ -1634,9 +1749,32 @@ void Client::readDataHeader() getWinUpdate(); break; } + case SRVKEEPALIVE: + { + //keepalive packet, do nothing + break; + } + case SRVDISCONNECT: + { + KDRStdErr()<<"Server sent disconnect notification"<<KDR_ENDL; + slotDisconnect(); + break; + } + case UDPOPEN: + { + openUdpConnection(); + break; + } + case UDPFAILED: + { + KDRStdErr()<<"Server rejected UDP connection, trying one more time..."; + requestUdpFrames(); + break; + } default: { KDRStdErr()<<"Unsupported header type: "<<data_type; + freeMessageBuffer(); exitOnError(tr("Unsupported header type")); break; } @@ -1644,9 +1782,156 @@ void Client::readDataHeader() freeMessageBuffer(); } +int Client::findPacket(QList< DgramPacket* >* list, uint16_t seq) +{ + for(int i=0;i<list->size();++i) + { + if(list->at(i)->getPacketSeq()==seq) + return i; + + } + return -1; +} + + +void Client::readDgram() +{ + qint64 available=udpSocket->pendingDatagramSize(); + + // KDRStdErr()<<"Have available:"<<available<<KDR_ENDL; + QByteArray data; + data.resize(available); + qint64 read=udpSocket->readDatagram(data.data(),available); + if(read!=available) + { + KDRStdErr()<<"Read datagram failed, read "<<data.size()<<" from "<<available<<KDR_ENDL; + return; + } + if(data.size()<SRVDGRAMHEADERSIZE) + { + KDRStdErr()<<"DGRAM size too small"<<KDR_ENDL; + return; + } + uint32_t checksum=*((uint32_t*)data.constData()); + memset(data.data(), 0, 4); + uint32_t crc=crc32(0L, Z_NULL, 0); + crc=crc32(crc,(unsigned char*)data.constData(),available); + if(crc!=checksum) + { + KDRStdErr()<<"DGRAM checksum failed"<<KDR_ENDL; + return; + } + updateServerAlive(); + + uint16_t packSeq=*((uint16_t*)data.constData()+2); + uint16_t dgInPack=*((uint16_t*)data.constData()+3); + // uint16_t dgSeq=*((uint16_t*)data.constData()+4); + uint8_t dgtype=*((uint8_t*)data.constData()+10); + QList<DgramPacket*> *list; + uint16_t lastSeq; + QString stringType; + int32_t current_long; + int32_t last_long; + + switch (dgtype) + { + case ServerFramePacket: + lastSeq=serverFrameSeq; + stringType="Server frame packet"; + list=&serverFramePackets; + break; + case ServerRepaintPacket: + lastSeq=serverRepaintSeq; + stringType="Server repaint packet"; + list=&serverRepaintPackets; + break; + } + //this is for the case when the seq is going over the max of the uint16 + //don't think we can lose more than 1k Packets + current_long=packSeq; + last_long=lastSeq; + if(abs(current_long-last_long)>64535 && (current_long<1000) ) + { + current_long+=65536; + } + + if(abs(current_long-last_long)>64535 && (last_long<1000) ) + { + last_long+=65536; + } + + if(current_long<=last_long) + { + KDRStdErr()<<"Late "<<stringType<<" arrived: "<<packSeq<<" last: "<<lastSeq<<KDR_ENDL; + return; + } + int packetInd=findPacket(list, packSeq); + DgramPacket* packet; + if(packetInd==-1) + { + //new packet + packet=new DgramPacket(packSeq, dgInPack); + list->append(packet); + packetInd=list->size()-1; + } + else + { +// KDRStdErr()<<"packet with seq "<<packSeq<<" has ind "<<packetInd<<KDR_ENDL; + packet=list->at(packetInd); + } + //if true, packet is complete + if(packet->addDgram(data)) + { + // KDRStdErr()<<"packet "<<packSeq<<" ready"<<KDR_ENDL; + if(dgtype==ServerFramePacket) + serverFrameSeq=packSeq; + else + serverRepaintSeq=packSeq; + getImageFrameFromDGPacket(packet->getData()); + //delete all broken or processed packets + while(!list->isEmpty()) + { + DgramPacket* first=list->takeFirst(); + if(first==packet) + { + delete first; + break; + } + delete first; + } + } +} + + +void Client::slotSynchronize() +{ + //not connected or server not supporting KEEPALIVE event + if(!connected || serverVersion<3) + return; + /*KDRStdErr()<<"Synchronizing: control seq:"<<serverControlSeq<<" frame seq:"<<serverFrameSeq<<" repaint seq:"<<serverRepaintSeq<< + " frame cache:"<<frameCache.size()<<" srv control:"<<serverControlPackets.size()<< + " srv frame:"<<serverFramePackets.size()<<" srv repaint:"<<serverRepaintPackets.size()<<" contr resend: "<< + requestedControlResend.size()<<" client event:"<<clientEventPackets.size()<<KDR_ENDL;*/ + + char evmsg[EVLENGTH]{}; + uint32_t etype; + etype=KEEPALIVE; + memcpy(evmsg,(char*)&etype,4); + sendEvent(evmsg); +} + +void Client::UDPDataArrived() +{ +// KDRStdErr()<<"Got udp data"<<KDR_ENDL; + updateServerAlive(); + while(((QUdpSocket*)udpSocket)->hasPendingDatagrams()) + readDgram(); +} + void Client::dataArrived() { + updateServerAlive(); // KDRStdErr()<<"Have available:"<<clientSocket->bytesAvailable(); if(!bytesLeftToRead) { @@ -1773,6 +2058,17 @@ void Client::checkServerVersion() KDRStdErr(false)<<"Server Version "<<serverVersion<<" doesn't support rootless mode. Please update the server package"<<KDR_ENDL; slotDisconnect(); } + if(serverVersion>=8) + { + checkSrvAliveTimer=new QTimer(this); + connect(checkSrvAliveTimer, SIGNAL(timeout()),this, SLOT(slotCheckIfServerIsAlive())); + checkSrvAliveTimer->start(SERVERALIVETIMEOUT*1000); + QTimer* t=new QTimer(this); + connect(t, SIGNAL(timeout()), this, SLOT(slotSynchronize())); + t->start(5000); + if(udpFrames) + requestUdpFrames(); + } } void Client::initGeometry() @@ -1808,15 +2104,17 @@ void Client::socketDisconnected() void Client::socketError(QAbstractSocket::SocketError ) { - KDRStdErr(false)<<clientSocket->errorString()<<KDR_ENDL; - exitOnError(clientSocket->errorString()); + QString errStr; + errStr=clientSocket->errorString(); + KDRStdErr(false)<<errStr<<KDR_ENDL; + exitOnError(errStr); } -void Client::exitOnError(const QString& /*message*/) +void Client::exitOnError(const QString& message) { - // QMessageBox::critical(this,tr("Error"),message); + KDRStdErr()<<"Exiting on error: "<<message<<KDR_ENDL; QApplication::closeAllWindows(); close(); QApplication::exit(-1); @@ -1826,16 +2124,19 @@ void Client::sendEvent(char* event) { if(!connected) return; + int ln; if(!clientSocket->isOpen()) - { return; - } - if(clientSocket->write(event, EVLENGTH)!=EVLENGTH) + ln=clientSocket->write(event, EVLENGTH); + + if(ln!=EVLENGTH) { - exitOnError(tr("Failed to send input event to server")); + KDRStdErr()<<"Failed to send input event to server, sent "<<ln<<" from "<<EVLENGTH<<KDR_ENDL; +// exitOnError(tr("Failed to send input event to server")); } } + void Client::moveEvent(QMoveEvent* ) { if(rootless) @@ -2320,6 +2621,17 @@ void Client::requestCacheRebuild() sendEvent(evmsg); } +void Client::requestFrame(uint32_t crc) +{ + char evmsg[EVLENGTH]{}; + uint32_t etype; + etype=RESENDFRAME; + memcpy(evmsg,(char*)&etype,4); + memcpy(evmsg+4,(char*)&crc,4); + sendEvent(evmsg); +} + + void Client::reinitCaches() { KDRStdErr(false)<<"Clearing all caches"<<KDR_ENDL; @@ -2334,6 +2646,65 @@ void Client::reinitCaches() currentCursor=0; if(!rootless) displayArea->repaint(0, 0, displayArea->width(), displayArea->height()); + serverFramePackets.clear(); + serverRepaintPackets.clear(); + KDRStdErr(false)<<"Done"<<KDR_ENDL; + +} + +void Client::closeEvent(QCloseEvent*) +{ + slotDisconnect(); +} + +void Client::slotCheckIfServerIsAlive() +{ + if(time(NULL)+SERVERALIVETIMEOUT>=lastServerPacketTime) + { + KDRStdErr()<<"Didn't recive any data from server since "<<time(NULL)-lastServerPacketTime<<" seconds, disconnecting...."<<KDR_ENDL; + slotDisconnect(); + } +} + +void Client::updateServerAlive() +{ + lastServerPacketTime=time(NULL); + if(checkSrvAliveTimer) + checkSrvAliveTimer->start(SERVERALIVETIMEOUT*1000); +} + +void Client::requestUdpFrames() +{ + if(udpConnectionAttempts>=3) + { + KDRStdErr()<<"Failed to establish UDP connection, continue over TCP"<<KDR_ENDL; + return; + } + char evmsg[EVLENGTH]{}; + uint32_t etype; + etype=OPENUDP; + memcpy(evmsg,(char*)&etype,4); + KDRStdErr()<<"Requesting UDP connection, attempt number "<<++udpConnectionAttempts<<KDR_ENDL; + sendEvent(evmsg); +} + +void Client::openUdpConnection() +{ + int32_t udp_port=*((uint16_t*)messageBuffer+2); + int32_t tmp_cookie[8]; + memcpy(tmp_cookie, messageBuffer+8,8*4); + KDRStdErr()<<"Server is listening on UDP port: "<<udp_port<<KDR_ENDL; + KDRStdErr(false)<<"Connecting to remote host "<<udpHost<<":"<<udp_port<<" over UDP"<<KDR_ENDL; + udpSocket->connectToHost(udpHost, udp_port); + if(!udpSocket->waitForConnected(3000)) + { + KDRStdErr(false)<<"Warning, can't establish UDP connection"<<KDR_ENDL; + } + else + { + KDRStdErr(false)<<"UDP connection established"<<KDR_ENDL; + udpSocket->write((char*)tmp_cookie,8*4); + } } #ifdef Q_OS_WIN bool Client::nativeEvent(const QByteArray &eventType, void *message, long *result) diff --git a/client.h b/client.h index 146e98e..fcaf5e3 100644 --- a/client.h +++ b/client.h @@ -21,10 +21,13 @@ #ifndef CLIENT_H #define CLIENT_H +#define KDRStdErr(a) Client::KDRStdErrFunc(a)<<__FILE__<<":"<< __LINE__<<":"<< __func__<<"(): " + //FEATURE_VERSION is not cooresponding to actual version of client //it used to tell server which features are supported by client //Changes 1 - 2: supporting extended selection and sending selection on demand -#define FEATURE_VERSION 2 +//Changes 2 - 3: support UDP protocol, sending keep alive packets +#define FEATURE_VERSION 3 //Version of client OS for same reason enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; @@ -58,10 +61,16 @@ enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; #define SELECTIONEVENT 9 #define CLIENTVERSION 10 #define DEMANDSELECTION 11 -//This event only sent by web client at the moment #define KEEPALIVE 12 #define CACHEREBUILD 13 #define WINCHANGE 14 +//client is going to disconnect +#define DISCONNECTCLIENT 15 +//ask to resend particular frame +#define RESENDFRAME 16 +//client is requesting UDP port for frames +#define OPENUDP 17 + #define ShiftMask (1<<0) #define LockMask (1<<1) @@ -80,6 +89,21 @@ enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; #define HEADER_SIZE 56 #define REGION_HEADER 64 +//max size for UDP dgram +#define UDPDGRAMSIZE 1200 + +//UDP Server DGRAM Header - 4B checksum + 2B packet seq number + 2B amount of datagrams + 2B datagram seq number + 1B type +#define SRVDGRAMHEADERSIZE (4+2+2+2+1) + + +//check if server is alive every 30 seconds +#define SERVERALIVETIMEOUT 30//sec + +//Types for UDP datagrams +enum ServerDgramType{ + ServerFramePacket, //dgram belongs to packet representing frame + ServerRepaintPacket, // dgram belongs to packet with screen repaint and the loss can be ignored +}; enum SelectionMime{STRING,UTF_STRING,PIXMAP}; enum WinState{UNCHANGED, DELETED, ICONIFIED}; @@ -160,8 +184,22 @@ public: enum SelectionType selection; }; +class DgramPacket +{ +public : + DgramPacket(uint16_t seq, uint16_t numOfDatagrams); + bool isComplete(){return complete;} + bool addDgram(QByteArray dgram); + int getNumberOfDatagrams(){return datagrams.size();}; + QByteArray getData(){return data;}; + uint16_t getPacketSeq(){return packetSeq;}; +private: + bool complete=false; + QVector<QByteArray> datagrams; + QByteArray data; + uint16_t packetSeq; +}; -class QTcpSocket; class DisplayArea; class QTimer; class MenuFrame; @@ -170,6 +208,8 @@ class QAction; class QLabel; class ScreenIdentifier; class ExtWin; +class QTcpSocket; +class QUdpSocket; class Client : public QMainWindow { @@ -191,7 +231,7 @@ public: void send_selnotify_to_server(SelectionType selection, SelectionMime mime); int max_chunk(); static QByteArray zuncompress(const char* data, uint compressed_size, uint size); - static QTextStream& KDRStdErr(bool dbg=true); + static QTextStream& KDRStdErrFunc(bool dbg=true); static QString QRectToStr(const QRect& rec); static QString QSizeToStr(const QSizeF& sz); void changeWindow(ExtWin* win, uint8_t newState=UNCHANGED); @@ -214,6 +254,8 @@ private slots: void socketDisconnected(); void socketError(QAbstractSocket::SocketError socketError); void dataArrived(); + void UDPDataArrived(); + void slotSynchronize(); void slotScreenAdded(QScreen* screen); void slotScreenRemoved(QScreen* screen); @@ -233,19 +275,21 @@ private slots: void slotSelectionChanged(QClipboard::Mode mode); void requestCacheRebuild(); void checkServerVersion(); + void slotCheckIfServerIsAlive(); public slots: void editWindowTitle(); private: enum{ HEADER, FRAMEREGION, REGIONDATA ,CURSORDATA, CURSORLIST, FRAMELIST, SELECTIONBUFFER, WINUPDATEBUFFER } currentDataType; - enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION, SERVER_VERSION, DEMANDCLIENTSELECTION,REINIT,WINUPDATE}; - + enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION, SERVER_VERSION, DEMANDCLIENTSELECTION, + REINIT, WINUPDATE, SRVKEEPALIVE, SRVDISCONNECT, CACHEFRAME, UDPOPEN, UDPFAILED}; void getServerversion(); void getClientSelection(); void setUseRandr(bool use); void exitOnError(const QString& message); void getImageFrame(); + void getImageFrameFromDGPacket(QByteArray data); void readDataHeader(); void getFrameRegion(); void getRegionImage(); @@ -260,6 +304,8 @@ private: void getWinUpdate(); void getWinUpdateBuffer(); void renderFrame(); + void requestUdpFrames(); + void openUdpConnection(); void freeMessageBuffer(); void setCursor(); void sendGeometryEvent(); @@ -267,6 +313,10 @@ private: void reinitCaches(); void initGeometry(); void setDisplayImage(); + void readDgram(); + void requestFrame(uint32_t crc); + int findPacket(QList<DgramPacket*>* list, uint16_t seq); + void updateServerAlive(); bool wantRepaint=false; bool hasUpdates=false; #ifndef Q_OS_LINUX @@ -283,6 +333,7 @@ private: int width=800; int height=600; QString host="localhost"; + QString udpHost="localhost"; int port=15000; @@ -294,6 +345,9 @@ private: int dispNumber=1; bool serverExtSelection=false; bool rootless=false; + bool udpFrames=false; + int udpConnectionAttempts=0; + time_t lastServerPacketTime=0; bool noresize=false; QString cookie; @@ -322,6 +376,7 @@ private: Qt::WindowFlags savedFlags; QTcpSocket* clientSocket=0l; + QUdpSocket* udpSocket=0l; int bytesLeftToRead=0; int bytesReady=0; char* messageBuffer=0l; @@ -355,10 +410,16 @@ private: QHash <uint32_t, QCursor*> cursorCache; QList <OutputChunk*> outputSelectionQueue; QList <ExtWin*> extWindows; + QList <DgramPacket*> serverFramePackets, serverRepaintPackets; + //last succesfully processed frame packet sequence + uint16_t serverFrameSeq=0-1; + //last succesfully processed repaint packet sequence + uint16_t serverRepaintSeq=0-1; int frameCount=0; QTimer* geometryDelay=0l; + QTimer* checkSrvAliveTimer=0l; QRect currentGeometry; QRect restoreGeometry; @@ -380,6 +441,7 @@ private: protected: void resizeEvent(QResizeEvent*); void moveEvent(QMoveEvent*); + void closeEvent(QCloseEvent* ); #ifdef Q_OS_WIN bool nativeEvent(const QByteArray &eventType, void *message, long *result); #endif diff --git a/debian/changelog b/debian/changelog index 1ef0d2f..b6be854 100644 --- a/debian/changelog +++ b/debian/changelog @@ -60,5 +60,7 @@ x2gokdriveclient (0.0.0.1-0x2go1) UNRELEASED; urgency=medium change background of display area, don't show window content and don't send geometry events while moving/resizing. - add --noresize option to disable size changing of the window for user. + - support for sending frames over UDP. Some improvements in detecting of + unchanged regions. -- Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Tue, 04 Jun 2019 11:10:43 +0200 diff --git a/displayarea.cpp b/displayarea.cpp index 3c2218a..24629fb 100644 --- a/displayarea.cpp +++ b/displayarea.cpp @@ -151,6 +151,13 @@ void DisplayArea::paintEvent(QPaintEvent* ev) { // Client::KDRStdErr()<<"Draw PIX from cache"<<KDR_ENDL; pix=client->getPixmapFromCache(currentFrame->crc); + if(pix.isNull()) + { + KDRStdErr()<<"Replacing with display image"<<KDR_ENDL; + QPixmap disp; + disp.convertFromImage(*client->getDisplayImage()); + pix=disp.copy(currentFrame->x, currentFrame->y, currentFrame->width, currentFrame->height); + } } if(currentFrame->x==-1 || currentFrame->y==-1) { -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdriveclient.git