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.shney...@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.gabr...@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 _______________________________________________ x2go-commits mailing list x2go-commits@lists.x2go.org https://lists.x2go.org/listinfo/x2go-commits