/*
 * QT Client for X2GoKDrive
 * Copyright (C) 2018-2023 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>
 * Copyright (C) 2018-2023 phoca-GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <QTcpSocket>
#include <QUdpSocket>
#include <QTime>
#include "client.h"
#include "displayarea.h"
#include <QApplication>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>
#include <QImage>
#include <QFile>
#include <QDesktopWidget>
#include <QMimeData>
#include <QBuffer>
#include <QMenuBar>
#include <QToolBar>
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
#include <QLoggingCategory>
#endif
#include "menuframe.h"

#include <QHBoxLayout>

#include <QLabel>
#include <QInputDialog>


#include <QResizeEvent>
#include <QMoveEvent>

#include <QGuiApplication>
#include <QScreen>
#include <QPushButton>
#include <QToolButton>
#include <QMenu>
#include <QClipboard>
#include "screenidentifier.h"
#include "extwin.h"

#ifdef Q_OS_LINUX
#include <xcbclip.h>
#endif

#include <zlib.h>

//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;
    this->height=height;
    this->xhot=xhot;
    this->yhot=yhot;
    this->serialNumber=serialNumber;
    this->dataSize=dataSize;
}

X2GoCursor::~X2GoCursor()
{
}


FrameRegion::FrameRegion(uint32_t source_crc, int32_t source_x, int32_t source_y, int32_t x, int32_t y,
                         uint32_t width, uint32_t height, uint32_t dataSize)
{
    this->source_crc=source_crc;
    this->source_x=source_x;
    this->source_y=source_y;
    this->x=x;
    this->y=y;
    this->width=width;
    this->height=height;
    this->dataSize=dataSize;
}

FrameRegion::~FrameRegion()
{
}


Frame::Frame(uint32_t width, uint32_t height, int32_t x, int32_t y,  uint32_t numOfRegions, uint32_t crc, uint32_t winId)

{
    this->crc=crc;
    this->x=x;
    this->y=y;
    this->width=width;
    this->height=height;
    this->winId=winId;
    this->numOfRegions=numOfRegions;
}

Frame::~Frame()
{
    while (!regions.isEmpty())
        delete regions.takeFirst();
}


OutputChunk::OutputChunk(SelectionType selection, SelectionMime mime)
{
    this->selection=selection;
    mimeData=mime;
    totalSize=0;
    firstChunk=lastChunk=false;
}

QString Client::QRectToStr(const QRect& rec)
{
    QString str;
    QTextStream(&str) << rec.x()<<","<<rec.y()<<" "<<rec.width()<<"x"<<rec.height();
    return str;
}

QString Client::QSizeToStr(const QSizeF& sz)
{
    QString str;
    QTextStream(&str) <<sz.width()<<"x"<<sz.height();
    return str;
}

QTextStream& Client::KDRStdErrFunc(bool dbg)
{
    static QTextStream out(stderr);
    static QString nls;
    nls.clear();
    static QTextStream nl(&nls);
    if(debug || !dbg)
    {
//         out<<KDR_ENDL;
        return out;
    }
    return nl;
}

bool Client::debug=false;

Client::Client()
{
    #ifdef Q_OS_LINUX
    clipboard=new XCBClip(this);
    #endif

    parseOptions();
    if(!rootless)
        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



    geometryDelay=new QTimer(this);
    geometryDelay->setSingleShot(true);
    geometryDelay->setInterval(500);

    connect(geometryDelay, SIGNAL(timeout()), this, SLOT(geometryChanged()));
    QGuiApplication* app=(QGuiApplication*)QGuiApplication::instance();

    connect(app, SIGNAL(screenAdded(QScreen *)), this, SLOT(slotScreenAdded(QScreen*)));
    connect(app, SIGNAL(screenRemoved(QScreen *)), this, SLOT(slotScreenRemoved(QScreen*)));

    connect(app, SIGNAL(primaryScreenChanged(QScreen *)), this, SLOT(geometryChanged()));

    QList <QScreen*> screens=app->screens();
    foreach(QScreen* screen, screens)
    {
        slotScreenAdded(screen);
    }
    QTimer::singleShot(10,this,SLOT(connectToServer()));
    if(rootless)
    {
        hide();
        ((QGuiApplication*)QGuiApplication::instance())->setQuitOnLastWindowClosed(false);
    }
}

Client::~Client()
{
    KDRStdErr()<<"Client destructor"<<KDR_ENDL;

    freeMessageBuffer();

    reinitCaches();
}

void Client::initDesktopMode()
{
    setGeometry(0,0,800,600);
    displayArea=new DisplayArea(this,this);
    displayArea->setObjectName("DisplayArea");
    displayArea->setStyleSheet("QFrame#DisplayArea{background-color:black;border-image:url(:/img/svg/x2gobg.svg) 0 0 0 0 stretch stretch;}");
    displayArea->show();
    setCentralWidget(displayArea);
    setWindowIcon(QIcon(":/img/png/x2goclient.png"));
    QMenu* menuCon=menuBar()->addMenu(tr("&Connection"));
    menu=menuBar()->addMenu(tr("&View"));
    FSMenuBar=new MenuFrame(this, menu);
    menuBar()->hide();

    connect(FSMenuBar, SIGNAL(editCaption()), this, SLOT(editWindowTitle()));
    connect(this, SIGNAL(windowTitleChanged(const QString &)), FSMenuBar, SLOT(setCaption(const QString &)));

    actRandr=menu->addAction(QIcon(":/img/svg/randr.svg"), tr("Multiple monitors"),this, SLOT(slotEnableRandr()));
    actRandr->setCheckable(true);
    menu->addSeparator();
    for(int i=0;i<4;++i)
    {
        actDisp[i]=menu->addAction(QIcon(":/img/svg/view-fullscreen.svg"), tr("Monitor ")+QString::number(i+1) ,this, SLOT(slotDisplayFS()));
        connect(actDisp[i], SIGNAL(hovered()), this, SLOT (slotIdentifyScreen()));
    }

    actFS=menu->addAction(QIcon(":/img/svg/view-fullscreen.svg"), tr("All monitors"),this, SLOT(slotFS()));
    menu->addSeparator();
    actRestore=menu->addAction(QIcon(":/img/svg/view-restore.svg"), tr("Restore") ,this, SLOT(slotRestore()));
    menuBar()->addSeparator();

    actDisconnect=menuCon->addAction( tr("Disconnect"),this, SLOT(slotDisconnect()));
    actDisconnect->setIcon(QIcon(":/img/svg/network-disconnect.svg"));
    actDisconnect->setToolTip(tr("Disconnect"));
    connect(menu,SIGNAL(aboutToShow()), this, SLOT(slotSetupMenu()));
    setWindowTitle(mainWndTitle);
    FSMenuBar->enableViewMenu(!noresize);
    if(noresize)
    {
        setFixedSize(QSize(width,height));
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
        setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
#else
        setWindowFlags(windowFlags () | Qt::MSWindowsFixedSizeDialogHint);
        show ();
#endif
    }

}


void Client::slotIdentifyScreen()
{
    if(!menu->isVisible() || !menu->underMouse())
    {
        if(screenIdentifier)
            screenIdentifier->hide();
        return;
    }

    QAction* hoveredAction=menu->actionAt( menu->mapFromGlobal(QCursor::pos()));
    if(!hoveredAction)
    {
        if(screenIdentifier)
            screenIdentifier->hide();
        return;
    }

    int number=-1;

    for(int i=0;i<4;++i)
    {
        if(hoveredAction == actDisp[i])
        {
            number=i;
            break;
        }
    }
    if(number<0)
    {
        if(screenIdentifier)
            screenIdentifier->hide();
        return;
    }

    if(number >= QGuiApplication::screens().size())
    {
        KDRStdErr()<<"screen not connected";
        if(screenIdentifier)
            screenIdentifier->hide();
        return;
    }
    QRect geom=QGuiApplication::screens()[number]->geometry();
    if(!screenIdentifier)
    {
        screenIdentifier=new ScreenIdentifier(this, Qt::FramelessWindowHint|Qt::X11BypassWindowManagerHint|Qt::WindowStaysOnTopHint);
    }
    screenIdentifier->setText(tr("Monitor")+" "+QString::number(number+1));
    int x_pos=geom.width()/2-300;
    int y_pos=geom.height()/2-150;
    screenIdentifier->setFixedSize(600,300);
    screenIdentifier ->move(geom.x()+x_pos, geom.y()+y_pos);
    screenIdentifier->show();
    menu->show();
    //hide the identifier, when no screen actions are hovered
    QTimer::singleShot(100,this, SLOT(slotIdentifyScreen()));
}

void Client::editWindowTitle()
{
    bool ok;
    QString title=QInputDialog::getText(this,tr("Enter new window title"),tr("New window title:"),QLineEdit::Normal, windowTitle(),&ok);
    if(ok)
    {
        if(title.length()==0)
        {
            setWindowTitle(tr("X2GoKDrive Client"));
            return;
        }
        setWindowTitle(title);
    }
}


void Client::slotEnableRandr()
{
    setUseRandr(!useRandr);
}


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()
{
    for(int i=0;i<4;++i)
    {
        if(QObject::sender() == actDisp[i])
        {
            setFS(i);
            return;
        }
    }
    setFS(dispNumber);
}


void Client::slotFS()
{
    setFS(-1);
}

void Client::slotRestore()
{

    if(FSScreen==-1 || wasInRealFs)
    {
        setWindowFlags(Qt::Window);
        setWindowState(Qt::WindowFullScreen);
        showFullScreen();
        QTimer::singleShot(100,this, &Client::resizeToSaved);
    }
    wasInRealFs=false;
    isFs=false;
    FSScreen=-1;
    showNormal();
    resize(savedSize);
    move(savedPosition);
}

void Client::resizeToSaved()
{
#ifdef Q_OS_WIN
    setWindowFlags(Qt::Window);
    QTimer::singleShot(100,this, SLOT(showNormal()));
#endif
#ifdef Q_OS_LINUX
    setWindowState(Qt::WindowNoState);
    setWindowState(windowState() & ~Qt::WindowMaximized);
#endif
    KDRStdErr()<<"restore to "<<savedSize.width()<<"x"<<savedSize.height()<<" "<<savedPosition.x()<<","<<savedPosition.y()<<KDR_ENDL;
    resize(savedSize);
    move(savedPosition);
    showNormal();
}

void Client::slotResizeFSFinal()
{
    QRect geometry=((QGuiApplication*)QGuiApplication::instance())->screens()[0]->virtualGeometry();
    QSize sz=geometry.size();
    resize(sz);
    setVisible(true);
    raise();
    activateWindow();
    QTimer::singleShot(1000,this, SLOT(slotEnableRandr()));
}


void Client::resizeToFS()
{
    QRect geometry=((QGuiApplication*)QGuiApplication::instance())->screens()[0]->virtualGeometry();
#ifdef Q_OS_LINUX
    setWindowState(windowState() & ~Qt::WindowMaximized);
    setWindowState(Qt::WindowFullScreen);
    move(geometry.topLeft());
    QTimer::singleShot(100,this, SLOT(slotEnableRandr()));
    QTimer::singleShot(250,this, SLOT(slotResizeFSFinal()));
#endif
#ifdef Q_OS_WIN
    QSize sz=geometry.size();
    resize(sz);
    setVisible(true);
    raise();
    activateWindow();
#endif
}

void Client::slotRealFs()
{
    wasInRealFs=true;
    savedFlags=windowFlags();
#ifdef Q_OS_LINUX
    setWindowFlags(windowFlags()|Qt::X11BypassWindowManagerHint);
    QTimer::singleShot(100,this, &Client::resizeToFS);
    #endif
#ifdef Q_OS_WIN
    QTimer::singleShot(100,this,SLOT(showFullScreen()));
    QTimer::singleShot(200,this, &Client::resizeToFS);
#endif
}

void Client::setFS(int screenNumber)
{
    if(!isFs)
    {
        savedSize=geometry().size();
        savedPosition=pos();
    }
    else
    {
        showNormal();
    }
    KDRStdErr()<<"setting FS on screen"<<screenNumber;
    isFs=true;
    FSScreen=screenNumber;
    if(screenNumber==-1)
    {
        QRect geometry=((QGuiApplication*)QGuiApplication::instance())->screens()[0]->virtualGeometry();
        move(geometry.topLeft());
#ifdef Q_OS_LINUX
        setWindowState(Qt::WindowFullScreen);
#endif
        QTimer::singleShot(100,this, &Client::slotRealFs);
    }
    else
    {
        QList<QScreen*>screens=((QGuiApplication*)QGuiApplication::instance())->screens();
        if(screenNumber<screens.count())
        {
            resize(screens[screenNumber]->geometry().width()-50, screens[screenNumber]->geometry().height()-50);
            move(screens[screenNumber]->geometry().topLeft());
            QTimer::singleShot(100,this,SLOT(showFullScreen()));
        }

    }
}

void Client::slotSetupMenu()
{
    menu->setEnabled(clientSocket->isOpen());
    if(useRandr)
    {
        actRandr->setChecked(true);
    }
    else
    {
        actRandr->setChecked(false);
    }
    actRestore->setVisible(isFs);
    actFS->setVisible((!isFs));

    actDisp[0]->setText(tr("Monitor 1"));

    if(((QGuiApplication*)QGuiApplication::instance())->screens().count()==1)
    {
        actFS->setVisible(false);
        actDisp[0]->setText(tr("Fullscreen"));
    }

    for(int i=0;i<4;++i)
    {
        if(i<((QGuiApplication*)QGuiApplication::instance())->screens().count())
        {
            actDisp[i]->setVisible(true);
            if(isFs && (FSScreen==i || FSScreen==-1))
            {
                actDisp[i]->setVisible(false);
            }
        }
        else
        {
            actDisp[i]->setVisible(false);
        }
    }
}

bool Client::isDisplayPointer(QMouseEvent* event)
{
    if(rootless)
        return true;
    if(event->x() > FSMenuBar->x() && event->x() < FSMenuBar->x() + FSMenuBar->width() &&
       event->y() > FSMenuBar->y() && event->y() < FSMenuBar->y() + FSMenuBar->height() &&
       event->button()== Qt::NoButton && event->buttons() == 0)
    {
        FSMenuBar->setHasPointer(true);
        return false;
    }
    else
    {
        FSMenuBar->setHasPointer(false);
        return true;
    }
}

void Client::freeMessageBuffer()
{
    if(messageBuffer)
        delete []messageBuffer;
    messageBuffer=0l;
}

void Client::parseOptions()
{
    mainWndTitle=tr("X2GoKDrive Client");
    QStringList args=QCoreApplication::arguments();
    QString optFile;
    for(int i=0;i<args.count();++i)
    {
        KDRStdErr()<<args[i];
        if(args[i]=="--debug")
        {
            debug=true;
            KDRStdErr()<<"Running in debug mode";
            continue;
        }
        if(args[i]=="--noresize")
        {
            noresize=true;
            KDRStdErr()<<"Disable resizing";
            continue;
        }
        if(args[i]=="--udp-frames")
        {
            udpFrames=true;
            udpHost=args[++i];
            continue;
        }
        if(args[i]=="--connect")
        {
            host=args[++i];
            continue;
        }
        if(args[i]=="--port")
        {
            port=args[++i].toInt();
            continue;
        }
        if(args[i]=="--title")
        {
            mainWndTitle=args[++i];
            continue;
        }
        if(args[i]=="--width")
        {
            width=args[++i].toUInt();
            continue;
        }
        if(args[i]=="--height")
        {
            height=args[++i].toUInt();
            continue;
        }
        if(args[i]=="--fs")
        {
            fullscreen=true;
        }
        if(args[i]=="--randr")
        {
            useRandr=true;
        }
        if(args[i]=="--rootless")
        {
            rootless=true;
            useRandr=true;
        }
        if(args[i]=="--screen")
        {
            multidisp=true;
            dispNumber=args[++i].toUInt()-1;
        }
        if(args[i]=="--selection")
        {
            QString smode=args[++i];
            if(smode=="both")
            {
                clipMode=CLIP_BOTH;
            }
            else if(smode=="none")
            {
                clipMode=CLIP_NONE;
            }
            else if(smode=="client")
            {
                clipMode=CLIP_CLIENT;
            }
            else if(smode=="server")
            {
                clipMode=CLIP_SERVER;
            }
            else
            {
                KDRStdErr()<<"Unsupported selections mode: "<<smode;
            }
        }
        if(args[i].indexOf("option")!=-1)
        {
            QStringList parts=args[i].split(",");
            foreach(QString part, parts)
            {
                if(part.indexOf("options=")==0)
                {
                    optFile=part.replace("options=","");
                    #ifdef Q_OS_WIN
                    QStringList parts=optFile.split("options:");
                    optFile=parts[0]+"options";
                    #else
                    QStringList parts=optFile.split(":");
                    optFile=parts[0];
                    #endif
                    QString title=optFile;
                    title=title.replace("S-","X2GO-");
                    parts=title.split("/");
                    foreach(QString title, parts)
                    {
                        if(title.indexOf("X2GO-")==0)
                        {
                            mainWndTitle=title;
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
    KDRStdErr()<<"options file: "<<optFile<<KDR_ENDL;
    if(optFile.length())
    {
        QFile fl(optFile);
        if(fl.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            QTextStream in(&fl);
            QString str=in.readAll();
            QStringList parts=str.split(",");
            foreach(QString part, parts)
            {
                if(part.indexOf("connect=")==0)
                {
                    host=part.replace("connect=","");
                    continue;
                }
                if(part.indexOf("cookie=")==0)
                {
                    cookie=part.replace("cookie=","");
                    if(cookie.length()!=32)
                    {
                        KDRStdErr()<<"Wrong cookie: "<<cookie;
                        exitOnError(tr("Wrong cookie"));
                    }
                    continue;
                }
                if(part.indexOf("port=")==0)
                {
                    port=part.replace("port=","").toInt();
                    continue;
                }
            }
        }
        else
        {
            KDRStdErr()<<"Error! Can't open options File for reading "<<optFile;
            exitOnError(tr("Error! Can't open options File for reading"));
        }
    }
}

void Client::connectToServer()
{
//     setWindowTitle("X2GO SESSION");

    if(rootless)
    {
        hide();
    }
    KDRStdErr(false)<<"Connecting to remote host "<<host<<":"<<port<<" over TCP"<<KDR_ENDL;
    clientSocket->connectToHost(host, port);
}


QPixmap Client::getPixmapFromCache(uint32_t crc)
{
    if(!frameCache.contains(crc))
    {
        KDRStdErr()<<"GETPIXMAP: frame "<<KDR_HEX<<crc<<" not found in cache"<<KDR_ENDL;
        if(serverVersion<5)
            return QPixmap();
        if(serverVersion<8)
        {
            requestCacheRebuild();
            return QPixmap();
        }
        requestFrame(crc);
        return QPixmap();
    }
    return frameCache[crc];
}

void Client::renderFrame()
{
    QPainter dispImagePainter;

    if(!currentFrame->crc)
    {
        //we got a whole display copy
        if(currentFrame->x==-1||currentFrame->y==-1)
        {
            KDRStdErr()<<"got initial Image: "<<currentFrame->width<<"x"<<currentFrame->height<<" "<<QSizeToStr(currentFrame->regions[0]->pix.size())<<KDR_ENDL;
            if(!rootless)
            {
                displayArea->resize(currentFrame->width, currentFrame->height);
                this->resize(currentFrame->width, currentFrame->height);
            }
            displayImage=QImage(currentFrame->width, currentFrame->height, QImage::Format_RGB32);
        }
    }
    else
    {
        if(currentFrame->numOfRegions)
        {
            QPainter painter;
            //We have new Image. We need to create a Pixmap and add to cache
            QPixmap pix(currentFrame->width, currentFrame->height);
            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];
                if(reg->source_crc)
                {
                    if(!frameCache.contains(reg->source_crc))
                    {
                        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],
                                       reg->source_x, reg->source_y, reg->width, reg->height);
                }
                else
                {
                    //KDRStdErr()<<"REG:"<<reg->x<<reg->y<<reg->width<< reg->height;
                    painter.drawPixmap(reg->x,reg->y, reg->pix);
                }
            }
            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++;
        }
    }
    wantRepaint=true;
    if(!rootless)
        displayArea->repaint(currentFrame->x, currentFrame->y, currentFrame->width, currentFrame->height);
    else
    {
        if(currentFrame->winId)
        {
//             KDRStdErr()<<"Paint currentFrame: "<<currentFrame->x<<":"<<currentFrame->y<<" "<< currentFrame->width<<"x"<< currentFrame->height<<KDR_HEX<<" for WIndow: "<<currentFrame->winId<<KDR_ENDL;
            ExtWin* win=findExtWinById(currentFrame->winId);
            if(win)
            {
                QPoint kdrPos=win->virtualToKdrivePosition(win->geometry().topLeft());
                win->getDisplayArea()->repaint(currentFrame->x-kdrPos.x(), currentFrame->y-kdrPos.y(), currentFrame->width, currentFrame->height);
            }
            else
            {
                KDRStdErr()<<"Window not found: "<<KDR_HEX<<currentFrame->winId<<KDR_ENDL;
            }
        }
        else
        {
            foreach(ExtWin* win, extWindows)
            {
                QPoint kdrPos=win->virtualToKdrivePosition(win->geometry().topLeft());
                win->getDisplayArea()->repaint(currentFrame->x-kdrPos.x(), currentFrame->y-kdrPos.y(), currentFrame->width, currentFrame->height);
            }
        }
    }
    setUptodate();
    setDisplayImage();
}

void Client::setDisplayImage()
{
    Frame* currentFrame=this->getCurrentFrame();
    QPixmap pix;
    if(!currentFrame->crc)
    {
        //                 Client::KDRStdErr()<<"Draw DISPLAY PIX "<<KDR_ENDL;
        pix=currentFrame->regions[0]->pix;
        //             disp=true;
    }
    else
    {
        //                 Client::KDRStdErr()<<"Draw PIX from cache"<<KDR_ENDL;
        pix=this->getPixmapFromCache(currentFrame->crc);
    }
    if(currentFrame->x==-1 || currentFrame->y==-1)
    {
        currentFrame->x=currentFrame->y=0;
    }
    QPainter painter;
    painter.begin(this->getDisplayImage());
    painter.drawPixmap(currentFrame->x,currentFrame->y,pix);
    painter.end();
}

void Client::setUptodate()
{
    wantRepaint=false;
    hasUpdates=true;
}



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(&region->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)
        delete currentFrame;
    uint32_t winId=0;
    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);

    //     KDRStdErr()<<"got frame "<<currentFrame->crc<<currentFrame->numOfRegions;
    if(!currentFrame->numOfRegions)
    {
        //we have no regions, render frame
        renderFrame();
    }
    else
    {
        //read first region
        bytesReady=0;
        bytesLeftToRead=REGION_HEADER;
        currentDataType=FRAMEREGION;
    }
}



void Client::getCursorImage()
{

    //get cursor image from buffer

    //     KDRStdErr()<<"got cursor image";

    QCursor* cursor;
    if(currentCursor->dataSize == (uint32_t) currentCursor->width*currentCursor->height*4)
    {
        //         KDRStdErr()<<"get ARGB cursor";
        QImage img((uchar*)messageBuffer, currentCursor->width, currentCursor->height, QImage::Format_ARGB32);
        cursor=new QCursor(QPixmap::fromImage(img), currentCursor->xhot, currentCursor->yhot);
    }
    else
    {
        cursor=new QCursor();
    }
    cursorCache.insert(currentCursor->serialNumber, cursor);
    freeMessageBuffer();
    if(!rootless)
        displayArea->setCursor(*cursor);
    else
    {
        for(int i=0;i<extWindows.count();++i)
        {
            extWindows[i]->getDisplayArea()->setCursor(*cursor);
        }
    }
}


void Client::setCursor()
{
}

void Client::getServerversion()
{
    serverVersion=*((uint16_t*)messageBuffer+2);
    serverExtSelection = (serverVersion>1);

    KDRStdErr(false)<<"server version: "<<serverVersion<<KDR_ENDL;
    initGeometry();
    checkServerVersion();
}

void Client::getClientSelection()
{
    uint16_t sel=*((uint16_t*)messageBuffer+2);
    SelectionType selection=PRIMARY;
    if(sel)
        selection=CLIPBOARD;
    KDRStdErr()<<"server demands data for "<<selection;
#ifdef Q_OS_LINUX
    clipboard->requestSelectionData(selection);
#else
    sendSelectionToServer(selection);
#endif

}



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));

    //     KDRStdErr()<<"got cursor with serial"<<currentCursor->serialNumber<<" size: "<<currentCursor->dataSize;


    if(!currentCursor->dataSize)
    {
        //we don't have data, set cursor
        if(!cursorCache.contains(currentCursor->serialNumber))
        {
            KDRStdErr()<<"cursor not found: "<<currentCursor->serialNumber<<KDR_ENDL;
            if(serverVersion<5)
                exitOnError(tr("Cursor not found in cache"));
            else
                requestCacheRebuild();
            return;
        }
        if(!rootless)
            displayArea->setCursor(*cursorCache[currentCursor->serialNumber]);
        else
        {
            for(int i=0;i<extWindows.count();++i)
            {
                extWindows[i]->getDisplayArea()->setCursor(*cursorCache[currentCursor->serialNumber]);
            }
        }
    }
    else
    {
        //getCursor image
        bytesReady=0;
        bytesLeftToRead=currentCursor->dataSize;
        currentDataType=CURSORDATA;
        freeMessageBuffer();
    }
}

void Client::getSelectionBuffer()
{
#ifndef Q_OS_LINUX
//if using qt class, not supporting on demand load of data
    setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed_size, selectionSize, messageBuffer);
#else
    clipboard->setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed_size, selectionSize, messageBuffer);
#endif
    freeMessageBuffer();
}


QByteArray Client::zuncompress(const char* data, uint compressed_size, uint size)
{
    unsigned char* out=new unsigned char [size];

    z_stream stream;
    stream.zalloc = Z_NULL;
    stream.zfree = Z_NULL;
    stream.opaque = Z_NULL;

    stream.avail_in = compressed_size;
    stream.next_in = (unsigned char*)data;
    stream.avail_out = size;
    stream.next_out = out;

    inflateInit(&stream);
    inflate(&stream, Z_NO_FLUSH);
    inflateEnd(&stream);

    if(!stream.total_out || stream.total_out != size)
    {
        KDRStdErr()<<"zlib decompression error, "<<"output size: "<<stream.total_out<<", expected: "<<size;
    }
    QByteArray ba( (const char*)out, size);
    delete[] out;
    return ba;
}


#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
    //     KDRStdErr()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size<<notify;


    if(firstChunk)
    {
        selData.clear();
        selMime=mime;
        total_compressed=0;
    }

    if(!compressed)
        selData.append(data,size);
    else
    {
        total_compressed+=compressed;
        selData.append(zuncompress(data, compressed, size));
        //         KDRStdErr()<<"uncompress from "<<compressed<<" to "<<size;
    }

    if(lastChunk )
    {
        if(notify)
        {
            KDRStdErr()<<"Got selection notify from server";
        }
        else
        {
            //             KDRStdErr()<<"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;
                }
            }
        }
    }
}

#endif
void Client::getDeletedCursorsList()
{
    //process list from messageBuffer
//     KDRStdErr()<<"get deleted cursors: "<<deletedCursorsSize;
    for(uint i=0;i<deletedCursorsSize;++i)
    {
        uint32_t serial=*((uint32_t*)messageBuffer+i);
        if(!cursorCache.contains(serial))
        {
            KDRStdErr()<<"cursor not found in cache: "<<serial<<KDR_ENDL;
            if(serverVersion>5)
            {
                requestCacheRebuild();
                return;
            }
            else
                continue;
        }
        delete cursorCache[serial];
        cursorCache.remove(serial);
    }
    freeMessageBuffer();
}

ExtWin* Client::findExtWinById(uint32_t extWinId)
{
    for(QList<ExtWin*>::iterator j=extWindows.begin(); j!=extWindows.end(); ++j)
    {
        if((*j)->getExtWinId()== extWinId)
        {
            return (*j);
        }
    }
    return NULL;
}

void Client::repaintAllWindows()
{
    foreach(ExtWin* w, extWindows)
    {
        QTimer::singleShot(10, w->getDisplayArea(), SLOT(repaint()));
    }
}

void Client::getWinUpdateBuffer()
{
    //process window updates  from messageBuffer
//     KDRStdErr()<<"get winupdate buffer size: "<<winUpdateSize<<KDR_ENDL;
    int readFromBuf=0;
    while(readFromBuf < winUpdateSize)
    {
        uint32_t extWinId=*((uint32_t*)(messageBuffer+readFromBuf));
        readFromBuf+=sizeof(uint32_t);
        uint8_t state=*((uint8_t*)(messageBuffer+readFromBuf));
        readFromBuf+=sizeof(uint8_t);

        ExtWin* win;
        //if the window is not a new one, check if it exists in the list
        if(state!=2)
        {
            win=findExtWinById(extWinId);
            if(!win)
            {
                if(state==3)
                {
                    KDRStdErr()<<"Error: deleted Window "<<KDR_HEX<<extWinId<<" not found in the list"<<KDR_DEC<<KDR_ENDL;
                    freeMessageBuffer();
                    return;
                }
                else
                {
                    KDRStdErr()<<KDR_ENDL<<KDR_ENDL<<"WARNING: updated Window "<<KDR_HEX<<extWinId<<" not found in the list, considering it as a new one"<<KDR_DEC<<KDR_ENDL<<KDR_ENDL<<KDR_ENDL;
                    state=2;
                }
            }
        }
        if(state==3)
        {
//             KDRStdErr()<<"win deleted: "<<KDR_HEX<<extWinId<<KDR_ENDL;
            extWindows.removeAll(win);
            win->close();
            delete win;
            repaintAllWindows();
        }
        else
        {
            int16_t x, y;
            uint16_t w,h,minw,minh,bw;
            uint8_t visibility;
            uint8_t winType;
            uint16_t nameSize;
            uint32_t icon_size;
            QPixmap icon;
            QString name;
            uint32_t parId=*((uint32_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint32_t);
            uint32_t nextSibId=*((uint32_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint32_t);
            uint32_t transWinId=*((uint32_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint32_t);
            x=*((int16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            y=*((int16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            w=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            h=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            minw=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            minh=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            bw=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);

            visibility=*((uint8_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint8_t);

            winType=*((uint8_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint8_t);

            nameSize=*((uint16_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint16_t);
            if(nameSize)
            {
                name=QString::fromUtf8(messageBuffer+readFromBuf, nameSize);
                readFromBuf+=nameSize;
            }
            icon_size=*((uint32_t*)(messageBuffer+readFromBuf));
            readFromBuf+=sizeof(uint32_t);
            if(icon_size)
            {
                if(!icon.loadFromData((const uchar*)(messageBuffer+readFromBuf), icon_size))
                {
                    KDRStdErr()<<"Failed load icon from bytes: "<<icon_size;
                }
                else
                {
//                     KDRStdErr()<<"load icon: "<<icon.width()<<"x"<<icon.height();
                }
                readFromBuf+=icon_size;
            }
            if(state==1)
            {
//                 KDRStdErr()<<"win changed: "<<KDR_HEX<<wptr;
            }
            else
            {
                Qt::WindowFlags flags;
                switch(winType)
                {
                    case WINDOW_TYPE_DIALOG:
                        winType=WINDOW_TYPE_DIALOG;
                        flags=Qt::Dialog;
//                         KDRStdErr()<<"new win: "<<KDR_HEX<<extWinId<<" Setting dialog flags";
                        break;
                    case WINDOW_TYPE_SPLASH:
                        flags=Qt::SplashScreen;
                        break;
                    case WINDOW_TYPE_UTILITY:
                    case WINDOW_TYPE_COMBO:
                    case WINDOW_TYPE_POPUP_MENU:
                    case WINDOW_TYPE_DROPDOWN_MENU:
                        flags=Qt::Window|Qt::FramelessWindowHint|Qt::BypassWindowManagerHint|Qt::X11BypassWindowManagerHint|Qt::WindowStaysOnTopHint;
                        break;
                    case WINDOW_TYPE_TOOLTIP:
                        flags=Qt::ToolTip;
                        break;
                    default:
                        flags=Qt::Window;
                }

//                 KDRStdErr()<<"new win: "<<KDR_HEX<<extWinId;
                if(parId==0)
                {
//                     KDRStdErr()<<"win is top window: "<<KDR_HEX<<parId;
                    win=new ExtWin(extWinId,this, 0, winType, flags);
                    win->setParent(0);
                }
                else
                {
                    ExtWin* parentWin=findExtWinById(parId);
//                     KDRStdErr()<<"win has parent!!!!!: "<<KDR_HEX<<parId;
                    if(!parentWin)
                    {
                        KDRStdErr()<<"parent Win not found in list: "<<KDR_HEX<<parId<<KDR_ENDL;
                        parentWin=(ExtWin*) this;
                    }
                    win=new ExtWin(extWinId,this, 0, winType, flags);
                    win->setParent(0);
                }
                if(transWinId)
                {
                    ExtWin* transWin=findExtWinById(transWinId);
                    if(!transWin)
                    {
//                         KDRStdErr()<<"trans Win not found in list: "<<KDR_HEX<<transWinId;
                    }
                    else
                    {
//                          KDRStdErr()<<"trans Window: "<<KDR_HEX<<transWinId<<" me: "<<extWinId<<KDR_ENDL;
//                         win->setParent(transWin);
                        win->setTransWinId(transWinId);
                        if(winType==WINDOW_TYPE_DIALOG)
                        {
                            win->setModality(ExtWin::MOD_SINGLE);
                            win->setWindowIcon(transWin->windowIcon());
                        }
                    }
                }
//                 KDRStdErr()<<"my Geometry: "<<KDR_HEX<<QRectToStr(win->geometry());


                win->setParentId(parId);
//                 KDRStdErr()<<"Place above"<<KDR_HEX<<nextSibPtr;
                win->setNextSibId(nextSibId);
                extWindows.append(win);
                if(minh&&minw)
                {
                    win->setMinimumWidth(minw);
                    win->setMinimumHeight(minh);
                }
                win->resize(w,h);
                win->moveWinFromServer(win->kdriveToVirtualPosition(QPoint(x,y)));
//                 Client::KDRStdErr()<<"new win pos: "<<x<<":"<<y<<" translated to "<< win->kdriveToVirtualPosition(QPoint(x,y)).x()<<":"<<win->kdriveToVirtualPosition(QPoint(x,y)).y()<<KDR_ENDL;
                win->showNormal();
                win->raise();
                win->activateWindow();
                repaintAllWindows();
                if(!icon.isNull())
                {
                    win->setWindowIcon(icon);
                }
                //hide windows with visibility==2 from taskbar
                if(winType == WINDOW_TYPE_NORMAL)
                {
                    if(visibility==2)
                    {
                        flags=Qt::SubWindow;
                        win->setWindowFlags(flags);
                        Client::KDRStdErr()<<"Window with visibility == 2, hiding from taskbar"<<KDR_ENDL;
                    }
                }

//                 win->update();
            }
            QPoint adjustedWinPos=win->kdriveToVirtualPosition(QPoint(x,y));
//             KDRStdErr()<<KDR_DEC<<name<<" "<<x<<":"<<y<<" "<<w<<"x"<<h<<" min size: "<<minw<<"x"<<minh<<" bw - "<<bw<<" "<<visibility<<KDR_ENDL;
            if(win->geometry().width() != w || win->geometry().height() != h)
                win->resize(w,h);
            if(win->geometry().x()!=adjustedWinPos.x() || win->geometry().y()!=adjustedWinPos.y())
                win->moveWinFromServer(adjustedWinPos);
            if(win->windowTitle()!=name)
                win->setWindowTitle(name);
//             win->raise();
            if(win->getParentId()!=parId)
            {
                //set new parent and remap window
                win->setParentId(parId);
                KDRStdErr()<<"Reparent window"<<KDR_ENDL;
            }
            if(win->getNextSibId()!=nextSibId)
            {
                // set sib and restack windows
                if(!win->getNextSibId() && nextSibId && winType == WINDOW_TYPE_NORMAL)
                {
                    win->activateWindow();
                    win->raise();
                    win->showNormal();
                }
                win->setNextSibId(nextSibId);
//                 KDRStdErr()<<"Check if need to Restack window???";
            }
        }
    }
    freeMessageBuffer();
}

void Client::getDeletedFramesList()
{
    //process list from messageBuffer
//     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<<KDR_ENDL;
            continue;
        }
        //         KDRStdErr()<<"deleting frame from cache with crc"<<KDR_HEX<<crc;
        QPixmap pix=frameCache[crc];
        cacheSize-=pix.width()*pix.height()*pix.depth()/8;

        frameCache.remove(crc);
    }
    freeMessageBuffer();
}

const QList<ExtWin*> Client::getSiblings(ExtWin* win)
{
    QList<ExtWin*> siblings;
    for(int i=0;i<extWindows.count();++i)
    {
        if((extWindows[i] != win) && (win->getParentId() == extWindows[i]->getParentId()))
        {
            siblings<<extWindows[i];
        }
    }
    return siblings;
}


void Client::getWinUpdate()
{
    //get list of updated windows
    bytesReady=0;
    winUpdateSize=bytesLeftToRead=*((uint32_t*)messageBuffer+1);
    currentDataType=WINUPDATEBUFFER;
    freeMessageBuffer();
}

void Client::getDeletedCursors()
{
    //get list of deleted cursors
    bytesReady=0;
    deletedCursorsSize=*((uint32_t*)messageBuffer+1);
    bytesLeftToRead=deletedCursorsSize * sizeof(uint32_t);
    currentDataType=CURSORLIST;
    freeMessageBuffer();
}

void Client::getSelection()
{
    bytesReady=0;
    selectionClipboard=CLIPBOARD;
    if( *((uint32_t*)messageBuffer+1 ) != CLIPBOARD)
    {
        selectionClipboard=PRIMARY;
    }
    selectionFormat=PIXMAP;
    if(*((uint32_t*)messageBuffer+2) != PIXMAP)
    {
        selectionFormat=UTF_STRING;
    }

    selectionSize=*((uint32_t*)messageBuffer+3);

    if(serverSupportsExtSelection())
    {
        firstChunk=*((uint32_t*)messageBuffer+4);
        lastChunk=*((uint32_t*)messageBuffer+5);
        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_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<<KDR_ENDL;
    freeMessageBuffer();
}


void Client::getDeletedFrames()
{
    //get list of deleted frames
    bytesReady=0;
    deletedFramesSize=*((uint32_t*)messageBuffer+1);
    bytesLeftToRead=deletedFramesSize * sizeof(uint32_t);
    currentDataType=FRAMELIST;
    freeMessageBuffer();
}


void Client::getRegionImage()
{
//     KDRStdErr()<<"got image for region "<<currentFrame->regions.count()-1<<"from"<<currentFrame->numOfRegions;

    if(currentFrame->regions.last()->pix.loadFromData((uchar*)messageBuffer, currentFrame->regions.last()->dataSize))
    {
//         QString fname;
//         fname.sprintf("/tmp/client/%05d.JPG",++frame_ctr);
//         currentFrame->regions.last()->pix->save(fname);
    }
    else
    {
        KDRStdErr()<<"Image loading failed: "<<KDR_HEX<<currentFrame->crc;
/*        KDRStdErr()<<currentFrame->regions.last()->dataSize;
        QFile fl("/tmp/client/failed.jpg");
        fl.open(QFile::WriteOnly);
        fl.write((const char*)messageBuffer, currentFrame->regions.last()->dataSize);
        fl.close();
        exit(-1);*/
        if(serverVersion<5)
            exitOnError("Image loading failed");
        else
            requestCacheRebuild();
    }
    if(currentFrame->numOfRegions == (uint)currentFrame->regions.size())
    {
        //all regions are ready, render image;
        renderFrame();
    }
    else
    {
        //read next region
        bytesReady=0;
        bytesLeftToRead=REGION_HEADER;
        currentDataType=FRAMEREGION;
    }
}


void Client::getFrameRegion()
{
    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));


    currentFrame->regions.append(region);
//      KDRStdErr()<<"got region "<<currentFrame->regions.count()-1<<"from"
//      <<currentFrame->numOfRegions<<"source: "<<region->source_crc<<"size: "<<region->dataSize;
    if(!region->source_crc)
    {
        //we need to read the image data of region
        bytesReady=0;
        bytesLeftToRead=region->dataSize;
        currentDataType=REGIONDATA;
        freeMessageBuffer();
    }
    else
    {
        if(currentFrame->numOfRegions == (uint)currentFrame->regions.size())
        {
            //all regions are ready, render image;
            renderFrame();
        }
        else
        {
            //read next region
            bytesReady=0;
            bytesLeftToRead=REGION_HEADER;
            currentDataType=FRAMEREGION;
        }
    }
}


void Client::readDataHeader()
{
    uint32_t data_type=*((uint32_t*)messageBuffer);

    switch(data_type)
    {

        case FRAME:
        {
//             KDRStdErr()<<"frame";
            getImageFrame();
            break;
        }
        case CURSOR:
        {
//             KDRStdErr()<<"cursor";
            getCursor();
            break;
        }
        case DELETEDFRAMES:
        {
//             KDRStdErr()<<"deleted frames";
            getDeletedFrames();
            break;
        }
        case DELETEDCURSORS:
        {
//             KDRStdErr()<<"deleted cursors";
            getDeletedCursors();
            break;
        }
        case SELECTION:
        {
//             KDRStdErr()<<"Get Selection";
            getSelection();
            break;
        }
        case SERVER_VERSION:
        {
            getServerversion();
            break;
        }
        case DEMANDCLIENTSELECTION:
        {
            getClientSelection();
            break;
        }
        case REINIT:
        {
            reinitCaches();
            break;
        }
        case WINUPDATE:
        {
            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;
        }
    }
    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)
    {
        bytesLeftToRead=HEADER_SIZE;
        bytesReady=0;
        currentDataType=HEADER;
        freeMessageBuffer();
    }
    if(!messageBuffer)
    {
//         KDRStdErr()<<"init message buffer: "<<bytesLeftToRead;
        messageBuffer=new char[bytesLeftToRead];
    }
//     KDRStdErr()<<"trying to read bytes:"<<bytesLeftToRead;
    int length=clientSocket->read(messageBuffer+bytesReady, bytesLeftToRead);
    bytesLeftToRead-=length;
    bytesReady+=length;

    if(!bytesLeftToRead)
    {
        switch(currentDataType)
        {
            case HEADER:
            {
//                 KDRStdErr()<<"header";
                readDataHeader();
                break;
            }
            case FRAMEREGION:
            {
//                 KDRStdErr()<<"frame region";
                getFrameRegion();
                break;
            }
            case REGIONDATA:
            {
//                 KDRStdErr()<<"region data";
                getRegionImage();
                break;
            }
            case CURSORDATA:
            {
//                 KDRStdErr()<<"cursor data";
                getCursorImage();
                break;
            }
            case SELECTIONBUFFER:
            {
//                 KDRStdErr()<<"selection";
                getSelectionBuffer();
                break;
            }
            case CURSORLIST:
            {
//                 KDRStdErr()<<"cursor list";
                getDeletedCursorsList();
                break;
            }
            case FRAMELIST:
            {
//                 KDRStdErr()<<"frame list";
                getDeletedFramesList();
                break;
            }
            case WINUPDATEBUFFER:
            {
                getWinUpdateBuffer();
                break;
            }
            default:
            {
                KDRStdErr()<<"not ready";
                exitOnError(tr("This function not ready yet"));
            }
        }
    }

    if(clientSocket->bytesAvailable())
        dataArrived();
}


void Client::socketConnected()
{
    if(!rootless)
        displayArea->setStyleSheet("QFrame#DisplayArea{background-color:black;}");
    KDRStdErr(false)<<"Connected to server"<<KDR_ENDL<<"Established X server connection"<<KDR_ENDL;

    if(cookie.length())
    {
        if(cookie.length()!=32)
        {
            KDRStdErr(false)<<"Wrong length of cookie should be 32, not "<<cookie.length()<<KDR_ENDL;
            exitOnError(tr("Wrong cookie length"));
        }
        KDRStdErr()<<"Sending Cookie to server"<<KDR_ENDL;
        if(clientSocket->write(cookie.toLatin1().data(), 32)!=32)
        {
            KDRStdErr(false)<<"Failed to send auth cookie to server"<<KDR_ENDL;
            exitOnError(tr("Failed to send auth cookie to server"));
        }
    }
    else
    {
        KDRStdErr(false)<<"Not sending cookie to server"<<KDR_ENDL;
    }

    connected=true;

    // send client version
    sendClientVersion();
}

void Client::checkServerVersion()
{
    if(!serverVersion)
    {
        KDRStdErr(false)<<"Server Version is 0, please update your x2gokdrive package"<<KDR_ENDL;
        //if the server bversion is 0, we didn't init the geometry yet
        initGeometry();
    }
    if(serverVersion<6 && rootless)
    {
        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()
{
    if(rootless)
    {
        geometryChanged();
        return;
    }
    if(geometry().width() != width || geometry().height() != height )
        resize(width, height);
    currentGeometry=geometry();
    sendGeometryEvent();
    if(fullscreen)
    {
        QTimer::singleShot(1000, this, SLOT( slotFS()));
    }
    else if(multidisp && (dispNumber < QGuiApplication::screens().count()))
    {
        QTimer::singleShot(1000, this, SLOT( slotDisplayFS()));
    }

}

void Client::socketDisconnected()
{
    connected=false;
    KDRStdErr(false)<<"Disconnected from Server"<<KDR_ENDL;
    QApplication::closeAllWindows();
    close();
    QApplication::exit(0);
}

void Client::socketError(QAbstractSocket::SocketError )
{
    QString errStr;
    errStr=clientSocket->errorString();
    KDRStdErr(false)<<errStr<<KDR_ENDL;
    exitOnError(errStr);
}


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);

}
void Client::sendEvent(char* event)
{
    if(!connected)
        return;
    int ln;
    if(!clientSocket->isOpen())
        return;
    ln=clientSocket->write(event, EVLENGTH);

    if(ln!=EVLENGTH)
    {
        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)
        return;
    if(geometryDelay->isActive())
        geometryDelay->stop();
    geometryDelay->start();
}


void Client::resizeEvent(QResizeEvent* )
{
    if(rootless)
        return;
    if(geometryDelay->isActive())
        geometryDelay->stop();
    geometryDelay->start();
    FSMenuBar->adjustPosition();
}

void Client::resizeToOldSize()
{
    currentGeometry=restoreGeometry;
    sendGeometryEvent();
}

void Client::slotScreenAdded(QScreen* screen)
{
    KDRStdErr()<<"Add screen: "<<QRectToStr(screen->geometry())<<" Dimensions(mm) "<<QSizeToStr(screen->physicalSize());
    connect(screen, &QScreen::geometryChanged, this, &Client::geometryChanged);
    geometryChanged();
}

void Client::slotScreenRemoved(QScreen* screen)
{
    KDRStdErr()<<"Remove screen: "<<QRectToStr(screen->geometry());
    disconnect(screen, &QScreen::geometryChanged, this, &Client::geometryChanged);
    geometryChanged();
}

void Client::sendGeometryEvent()
{
    char evmsg[EVLENGTH]{};
    uint16_t width, height;
    uint32_t etype;
    width=currentGeometry.size().width();
    height=currentGeometry.size().height();
    etype=GEOMETRY;
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&width,2);
    memcpy(evmsg+6,(char*)&height,2);

    //evmsg+8 - 1 byte, index of primary screen

    //start from evmsg+9


    uint8_t i=0;
    for(;i<4;++i)
    {
        bool isPrimary=(i==primaryScreenIndex);
        QRect* scr=&ephyrScreens[i];
        KDRStdErr()<<"X2GoKDrive-"<<i<<" "<<scr->size().width()<<"x"<<scr->size().height()<<" "<<scr->topLeft().x()<<":"<<scr->topLeft().y()<<" isPrimary: "<<isPrimary<<KDR_ENDL;
        if(isPrimary)
        {
            memcpy(evmsg+8,(char*)&i,1);
        }
        if(i<=3)
        {
            uint16_t w,h;
            int16_t x,y;
            w=scr->size().width();
            h=scr->size().height();
            x=scr->topLeft().x();
            y=scr->topLeft().y();
            memcpy(evmsg+9+i*8, (char*)&w,2);
            memcpy(evmsg+9+i*8+2, (char*)&h,2);
            memcpy(evmsg+9+i*8+4, (char*)&x,2);
            memcpy(evmsg+9+i*8+6, (char*)&y,2);
        }
        else
        {
            KDRStdErr()<<"not supporting more then 4 displays yet, not sending this display to server";
        }
    }
    sendEvent(evmsg);
}


void Client::sendClientVersion()
{
    //sending the feature vesrion and OS version to the server
    char evmsg[EVLENGTH]{};
    uint16_t version, os;
    uint32_t etype;
    version=FEATURE_VERSION;
    os=OS_LINUX;
#ifdef Q_OS_WIN
    os=OS_WINDOWS;
#endif
#ifdef Q_OS_DARWIN
    os=OS_DARWIN
#endif
    etype=CLIENTVERSION;
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&version,2);
    memcpy(evmsg+6,(char*)&os,2);
    KDRStdErr(false)<<"Sending version: "<<version<<" OS: "<<os<<KDR_ENDL;
    sendEvent(evmsg);
}


void Client::changeWindow(ExtWin* win, uint8_t newState)
{
    char evmsg[EVLENGTH]{};
    uint32_t etype;
    uint32_t extWinId=win->getExtWinId();
    uint32_t sibId=win->getNextSibId();
    uint16_t x,y,w,h;
    uint8_t focus=win->getHasFocus();
    QPoint kdrPoint=win->virtualToKdrivePosition(QPoint(win->geometry().topLeft()));
    x=kdrPoint.x();
    y=kdrPoint.y();
    w=win->geometry().width();
    h=win->geometry().height();
    etype=WINCHANGE;
//     KDRStdErr()<<"win geom "<<w<<"x"<<h<<KDR_ENDL;
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&extWinId,4);
    memcpy(evmsg+8,(char*)&sibId,4);
    memcpy(evmsg+12,(char*)&x,2);
    memcpy(evmsg+14,(char*)&y,2);
    memcpy(evmsg+16,(char*)&w,2);
    memcpy(evmsg+18,(char*)&h,2);
    memcpy(evmsg+20,(char*)&focus,1);
    memcpy(evmsg+21,(char*)&newState,1);
    sendEvent(evmsg);
    //     QTimer::singleShot(1,win,SLOT(update()));
}

//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()
{
    if(!connected)
    {
        return;
    }

    QGuiApplication* app=(QGuiApplication*)QGuiApplication::instance();
    QRect newGeometry;
    if(rootless)
    {
        newGeometry=app->screens()[0]->availableVirtualGeometry();
        hide();
    }
    else
    {
        newGeometry.setTopLeft(geometry().topLeft());
        newGeometry.setSize(geometry().size());
    }

    KDRStdErr()<<"geometry changed: "<<QRectToStr(newGeometry)<<" "<<QRectToStr(currentGeometry)<<KDR_ENDL;

    QRect  newScreens[4];

    bool screensChanged=false;
    int screenIndex=0;
    if(useRandr)
    {
        QList <QScreen*> screens=app->screens();
        foreach(QScreen* screen, screens)
        {
            //         KDRStdErr()<<screen->geometry()<<primary;
            if(screen->geometry().intersects(newGeometry))
            {
                QRect inter=newGeometry.intersected(screen->geometry());
                newScreens[screenIndex++]=QRect(inter.topLeft().x()-newGeometry.topLeft().x(), inter.topLeft().y()-newGeometry.topLeft().y(), inter.width(), inter.height());
                //             KDRStdErr()<<"Virtual screen:"<<screenIndex-1<<newScreens[screenIndex-1];
                if(app->primaryScreen()==screen)
                {
                    if ( screenIndex != primaryScreenIndex)
                        screensChanged=true;
                    primaryScreenIndex=screenIndex;
                }
                //support max 4 screens now
                if(screenIndex>3)
                    break;
            }
        }
    }
    else
    {
        newScreens[0].setX(0);
        newScreens[0].setY(0);
        newScreens[0].setSize(newGeometry.size());
    }
    for(int i=0;i<4;++i)
    {
        if(newScreens[i].size()!=ephyrScreens[i].size())
            screensChanged=true;
        ephyrScreens[i]=newScreens[i];
    }

    bool geometryChanged=false;
    if(currentGeometry.size() != newGeometry.size())
    {
        geometryChanged=true;
    }

    currentGeometry=newGeometry;

    if(!screensChanged && !geometryChanged)
    {
        return;
    }

    //Not really happy with this solution
    //if our size not changed we won't get randr screen changed event even if we change outputs geometry
    //so we first resize window with width-1 and then resize it to original size
    if(!geometryChanged)
    {
        //         KDRStdErr()<<"Screen Changed without geometry, running resize hack!!!!!";
        restoreGeometry=currentGeometry;
        currentGeometry.setWidth(currentGeometry.width()-1);
        QTimer::singleShot(100,this, &Client::resizeToOldSize);
    }
    sendGeometryEvent();
}

void Client::setUseRandr(bool use)
{
    if(use!=useRandr)
    {
        useRandr=use;
        geometryChanged();
    }
}

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();

}

void Client::slotSelectionChanged(QClipboard::Mode mode)
{

#ifndef Q_OS_LINUX

    //we are not using QT Clipboard Class on Linux, seems that it has problems with INCR properties
    if(!connected)
        return;
    const QClipboard *clipboard = QGuiApplication::clipboard();
    if(mode == QClipboard::Clipboard && clipboard->ownsClipboard())
        return;
    if(mode == QClipboard::Selection && clipboard->ownsSelection())
        return;
    const QMimeData *mimeData = clipboard->mimeData(mode);
    KDRStdErr()<<"selection changed for "<<mode<<mimeData->formats().join(", ");

    SelectionType destination=PRIMARY;
    if(mode== QClipboard::Clipboard)
        destination=CLIPBOARD;

    SelectionMime mime;


    if(mimeData->hasImage())
    {
        KDRStdErr()<<"Have new Image";
        mime=PIXMAP;
    }
    else if(mimeData->hasText())
    {
        KDRStdErr()<<"Have new Text";
        mime=UTF_STRING;
    }
    else
    {
        KDRStdErr()<<"Unsupported MIME type in clipboard";
        return;
    }

    if(serverSupportsExtSelection())
    {
        //send notify to server
        send_selnotify_to_server(destination,mime);
    }
    else
    {
        //send data to server
        sendSelectionToServer(destination);
    }

#endif

}

#ifndef Q_OS_LINUX
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;

    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, "PNG");
        KDRStdErr()<<"Selection image size "<< data.size();
        mime=PIXMAP;
    }
    else if(mimeData->hasText())
    {
        data=clipboard->text(mode).toUtf8();
        KDRStdErr()<<"Selection Text "<< data.size();
        mime=UTF_STRING;
    }
    else
    {
        KDRStdErr()<<"Unsupported MIME type in clipboard";
        return;
    }

    if(!data.size())
    {
        KDRStdErr(false)<<"no data"<<KDR_ENDL;
        return;
    }

    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

void Client::sendOutputSelChunk()
{
    //sending the first chunk from output selection queue

    if(outputSelectionQueue.isEmpty())
        return;

    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
        KDRStdErr()<<"Server doesn't support extended selections";
        delete chunk;
        return;
    }

    char evmsg[EVLENGTH]{};
    uint32_t etype=SELECTIONEVENT;
    uint32_t size;
    uint8_t destination=chunk->selection;
    uint8_t mime=chunk->mimeData;
    uint8_t firstChunk=chunk->firstChunk;
    uint8_t lastChunk=chunk->lastChunk;
    uint32_t compressed_size=0;
    uint32_t totalSize=chunk->totalSize;

    size=chunk->data.size();

    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&size,4);
    memcpy(evmsg+8,(char*)&destination,1);
    memcpy(evmsg+9,(char*)&mime,1);

    //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_size,4);
        memcpy(evmsg+16,(char*)&totalSize,4);
        //         KDRStdErr()<<"size of chunk: "<<size<<" compressed: "<<compressed_size<<"total: "<<totalSize;
    }

    uint headerSize=10;
    if(serverSupportsExtSelection())
        headerSize=20;

//     KDRStdErr()<<"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,data_ptr,sentData);
    sendEvent(evmsg);
    while(sentData<size)
    {
        int msg_length=(size-sentData < EVLENGTH)?size-sentData:EVLENGTH;
        memcpy(evmsg, data_ptr+sentData, msg_length);
        sentData+=msg_length;
        sendEvent(evmsg);
    }
//     KDRStdErr()<<"sent: "<<sentData<<"from"<<size;
    delete chunk;
}

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
}

void Client::requestCacheRebuild()
{
    //sending the feature vesrion and OS version to the server
    char evmsg[EVLENGTH]{};
    uint32_t etype;
    etype=CACHEREBUILD;
    memcpy(evmsg,(char*)&etype,4);
    KDRStdErr(false)<<"Requesting cache rebuild"<<KDR_ENDL;
    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;
    cursorCache.clear();
    frameCache.clear();
    wantRepaint=false;
    if(currentFrame)
        delete currentFrame;
    if(currentCursor)
        delete currentCursor;
    currentFrame=0;
    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)
{
    if(eventType=="windows_generic_MSG" && !rootless)
    {
        MSG* ev = static_cast<MSG *>(message);
        switch((ev->message))
        {
            case WM_SETFOCUS:
            case WM_KILLFOCUS:
                //try to repaint the window to avoid update errors on Windows
                QTimer::singleShot(500, displayArea, SLOT(repaint()));
                break;
        }
    }
    return QMainWindow::nativeEvent(eventType, message, result);
}
#endif //Q_OS_WIN
