[X2Go-Commits] [x2gokdriveclient] 01/01: support for sending frames over UDP. Some improvements in detecting of unchanged regions.
git-admin at x2go.org
git-admin at x2go.org
Fri Dec 23 00:33:59 CET 2022
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 at 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 at 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
More information about the x2go-commits
mailing list