[X2Go-Commits] [x2gokdriveclient] 01/01: not use QClipboard class on Linux. It has problems mit INCR properties. Using XCB API instead.

git-admin at x2go.org git-admin at x2go.org
Thu Aug 20 22:37:24 CEST 2020


This is an automated email from the git hooks/post-receive script.

x2go pushed a commit to branch master
in repository x2gokdriveclient.

commit 5d196b1b1c77db9e65850419e126a313f6326892
Author: Oleksandr Shneyder <o.shneyder at phoca-gmbh.de>
Date:   Thu Aug 20 15:37:08 2020 -0500

    not use QClipboard class on Linux. It has problems mit INCR properties. Using XCB API instead.
---
 client.cpp           | 143 ++++++++-
 client.h             |  54 +++-
 debian/changelog     |   1 +
 displayarea.cpp      |   3 +-
 x2gokdriveclient.pro |   7 +-
 xcbclip.cpp          | 816 +++++++++++++++++++++++++++++++++++++++++++++++++++
 xcbclip.h            |  79 +++++
 7 files changed, 1088 insertions(+), 15 deletions(-)

diff --git a/client.cpp b/client.cpp
index e016ef6..a2178e6 100644
--- a/client.cpp
+++ b/client.cpp
@@ -53,7 +53,9 @@
 #include <QClipboard>
 #include "screenidentifier.h"
 
-
+#ifdef Q_OS_LINUX
+#include <xcbclip.h>
+#endif
 
 
 X2GoCursor::X2GoCursor(uint16_t width, uint16_t height, uint16_t xhot, uint16_t yhot, uint32_t serialNumber, uint32_t dataSize)
@@ -107,10 +109,22 @@ Frame::~Frame()
 }
 
 
+OutputChunk::OutputChunk(SelectionType selection, SelectionMime mime)
+{
+    this->selection=selection;
+    mimeData=mime;
+    compressed=false;
+    firstChunk=lastChunk=false;
+}
+
 
 
 Client::Client()
 {
+    #ifdef Q_OS_LINUX
+    clipboard=new XCBClip(this);
+    #endif
+
     setGeometry(0,0,800,600);
 
     displayArea=new DisplayArea(this);
@@ -130,7 +144,9 @@ Client::Client()
     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()) );
+#ifndef Q_OS_LINUX
     connect(QGuiApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(slotSelectionChanged(QClipboard::Mode)));
+#endif
 
 
 
@@ -795,6 +811,9 @@ void Client::getCursor()
 
 void Client::getSelectionBuffer()
 {
+#ifndef Q_OS_LINUX
+#warning check it
+//if using qt class, not supporting on demand load of data
     QClipboard::Mode mode=QClipboard::Clipboard;
     if(!selectionClipboard)
     {
@@ -804,22 +823,26 @@ void Client::getSelectionBuffer()
     else
         qDebug()<<"Got new Clipboard, format:"<<selectionFormat;
 
+    QClipboard* clipboard=QGuiApplication::clipboard();
 
 
 //     qDebug()<<messageBuffer;
     switch(selectionFormat)
     {
-        case STRING: QGuiApplication::clipboard()->setText(QString::fromLocal8Bit(messageBuffer, selectionSize), mode); break;
-        case UTF_STRING: QGuiApplication::clipboard()->setText(QString::fromUtf8(messageBuffer, selectionSize), mode);break;
+        case STRING: clipboard->setText(QString::fromLocal8Bit(messageBuffer, selectionSize), mode); break;
+        case UTF_STRING: clipboard->setText(QString::fromUtf8(messageBuffer, selectionSize), mode);break;
         case PIXMAP:
         {
             QPixmap pix;
             pix.loadFromData((const uchar*)messageBuffer, selectionSize);
-            qDebug()<<pix;
-            QGuiApplication::clipboard()->setImage(pix.toImage());
+//             qDebug()<<pix;
+            clipboard->setImage(pix.toImage(), mode);
             break;
         }
     }
+#else
+    clipboard->setInputSelectionData(selectionClipboard, selectionFormat, firstChunk, lastChunk, compressed, selectionSize, messageBuffer);
+#endif
     freeMessageBuffer();
 }
 
@@ -880,9 +903,34 @@ void Client::getDeletedCursors()
 void Client::getSelection()
 {
     bytesReady=0;
-    selectionClipboard=*((uint32_t*)messageBuffer+1);
-    selectionFormat=*((uint32_t*)messageBuffer+2);
+    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=*((uint32_t*)messageBuffer+6);
+    }
+    else
+    {
+        //if server doesn't support extended selection it'll send data in one chunk, uncompressed
+        firstChunk=true;
+        lastChunk=true;
+        compressed=false;
+    }
+
+
     currentDataType=SELECTIONBUFFER;
     bytesLeftToRead=selectionSize;
     qDebug()<<"Get Selection, is Clipboard"<<selectionClipboard<<selectionFormat<<selectionSize;
@@ -1360,8 +1408,10 @@ void Client::setUseRandr(bool use)
     }
 }
 
+#ifndef Q_OS_LINUX
 void Client::slotSelectionChanged(QClipboard::Mode mode)
 {
+//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();
@@ -1369,8 +1419,19 @@ void Client::slotSelectionChanged(QClipboard::Mode mode)
         return;
     if(mode == QClipboard::Selection && clipboard->ownsSelection())
         return;
-    qDebug()<<"selection changed for"<<mode;
     const QMimeData *mimeData = clipboard->mimeData(mode);
+    qDebug()<<"selection changed for"<<mode<<mimeData->formats();
+
+    //if server supports ext selection only sending notification that selection is changed and the list of supported mime types
+    if(serverSupportsExtSelection())
+    {
+
+    }
+    else
+    {
+        //server doesn't support ext selection we are sending clipboard data
+    }
+
     QByteArray data;
     //add size/mime/type of buffer and start copy data
     char evmsg[EVLENGTH]{};
@@ -1428,5 +1489,71 @@ void Client::slotSelectionChanged(QClipboard::Mode mode)
         sendEvent(evmsg);
     }
     qDebug()<<"sent: "<<sentData<<"from"<<size;
+}
+#endif
+
+void Client::sendOutputSelChunk()
+{
+    //sending the first chunk from output selection queue
+
+    if(outputSelectionQueue.isEmpty())
+        return;
+
+    OutputChunk* chunk=outputSelectionQueue.takeLast();
+    if(!serverSupportsExtSelection() && (!chunk->firstChunk || !chunk->lastChunk))
+    {
+        //selection has multiply chunks, but this server doesn't support ext selection, not sending anything
+        qDebug()<<"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;
+    uint8_t compressed=chunk->compressed;
+    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())
+    {
+        memcpy(evmsg+10,(char*)&firstChunk,1);
+        memcpy(evmsg+11,(char*)&lastChunk,1);
+        memcpy(evmsg+12,(char*)&compressed,1);
+        memcpy(evmsg+13,(char*)&totalSize,4);
+    }
+
+    uint headerSize=10;
+    if(serverSupportsExtSelection())
+        headerSize=17;
 
+//     qDebug()<<"SEND SELECTION"<<size<<destination<<mime;
+    uint32_t sentData=(size < EVLENGTH-headerSize)?size:EVLENGTH-headerSize;
+    memcpy(evmsg+headerSize,chunk->data.data(),sentData);
+    sendEvent(evmsg);
+    while(sentData<size)
+    {
+        int msg_length=(size-sentData < EVLENGTH)?size-sentData:EVLENGTH;
+        memcpy(evmsg, chunk->data.data()+sentData, msg_length);
+        sentData+=msg_length;
+        sendEvent(evmsg);
+    }
+//     qDebug()<<"sent: "<<sentData<<"from"<<size;
+    delete chunk;
+}
+
+void Client::addToSelectionOutput(OutputChunk* chunk)
+{
+    outputSelectionQueue.append(chunk);
 }
diff --git a/client.h b/client.h
index 9211384..fdb3f72 100644
--- a/client.h
+++ b/client.h
@@ -23,6 +23,7 @@
 
 #define EVLENGTH 41
 
+
 #define Button1Mask (1<<8)
 #define Button2Mask (1<<9)
 #define Button3Mask (1<<10)
@@ -64,12 +65,21 @@
 #define HEADER_SIZE 56
 #define REGION_HEADER 64
 
+
+enum SelectionMime{STRING,UTF_STRING,PIXMAP};
+enum SelectionType{PRIMARY,CLIPBOARD};
+
+
 #include <QMainWindow>
 #include <QAbstractSocket>
 #include <stdlib.h>
 #include <QClipboard>
 
 
+#ifdef Q_OS_LINUX
+class XCBClip;
+#endif
+
 class FrameRegion
 {
 public:
@@ -104,6 +114,19 @@ public:
     uint16_t height;
 };
 
+class OutputChunk
+{
+public:
+    OutputChunk(SelectionType selection, SelectionMime mimeType);
+    QByteArray data;
+    enum SelectionMime mimeData;
+    bool compressed;
+    bool firstChunk;
+    bool lastChunk;
+    uint totalSize;
+    enum SelectionType selection;
+};
+
 
 class QTcpSocket;
 class DisplayArea;
@@ -127,6 +150,10 @@ public:
     QPixmap getPixmapFromCache(uint32_t crc);
     QPixmap addPixmapToCache();
     bool isDisplayPointer(QMouseEvent* event);
+    void addToSelectionOutput(OutputChunk* chunk);
+    bool serverSupportsExtSelection(){return serverExtSelection;}
+    void sendOutputSelChunk();
+
 
 private slots:
     void slotIdentifyScreen();
@@ -151,7 +178,9 @@ private slots:
     void slotDisplayFS();
     void slotEnableRandr();
     void slotResizeFSFinal();
+#ifndef Q_OS_LINUX
     void slotSelectionChanged(QClipboard::Mode mode);
+#endif
 public slots:
     void editWindowTitle();
 
@@ -159,8 +188,6 @@ public slots:
 private:
     enum{ HEADER, FRAMEREGION, REGIONDATA ,CURSORDATA, CURSORLIST, FRAMELIST, SELECTIONBUFFER } currentDataType;
     enum HeaderType{ FRAME, DELETEDFRAMES, CURSOR, DELETEDCURSORS, SELECTION};
-    enum SelectionMime{STRING,UTF_STRING,PIXMAP};
-    enum SelectionType{PRIMARY,CLIPBOARD};
 
     void setUseRandr(bool use);
     void exitOnError(const QString& message);
@@ -192,6 +219,7 @@ private:
     bool fullscreen=false;
     bool multidisp=false;
     int dispNumber=1;
+    bool serverExtSelection=false;
     QString cookie;
 
     MenuFrame *FSMenuBar;
@@ -222,15 +250,29 @@ private:
     char* messageBuffer=0l;
     bool connected=false;
 
+
+    //input selection chunk variables
+    //size of current chunk
     uint32_t selectionSize;
-    uint32_t selectionFormat;
-    bool selectionClipboard;
+    //format of chunk, string or pix
+    SelectionMime selectionFormat;
+    //if true clipboard else primary
+    SelectionType selectionClipboard;
+    //if it's the first chunk in multiply sel
+    bool firstChunk;
+    //if it's the last chunk in multiply sel
+    bool lastChunk;
+    //if the chunk compressed
+    bool compressed;
+    //////////
 
     uint32_t deletedFramesSize=0;
     uint32_t deletedCursorsSize=0;
 
     QHash <uint32_t, QPixmap> frameCache;
     QHash <uint32_t, QCursor*> cursorCache;
+    QList <OutputChunk*> outputSelectionQueue;
+
     int frameCount=0;
 
     QTimer* geometryDelay=0l;
@@ -243,6 +285,10 @@ private:
     ScreenIdentifier *screenIdentifier=0l;
     QLabel* fr;
 
+#ifdef Q_OS_LINUX
+    XCBClip* clipboard;
+#endif
+
 protected:
     void resizeEvent(QResizeEvent*);
     void moveEvent(QMoveEvent*);
diff --git a/debian/changelog b/debian/changelog
index d5ec6d2..7e75b7b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -20,5 +20,6 @@ x2gokdriveclient (0.0.0.1-0x2go1) UNRELEASED; urgency=medium
     - force client to send geometry event if the size of the display area is not changed after intiation
       for server to send initial image.
     - identify screen, when user hovering the action in the multiple monitors menu.
+    - not use QClipboard class on Linux. It has problems mit INCR properties. Using XCB API instead.
 
  -- 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 855922a..bdb9e1a 100644
--- a/displayarea.cpp
+++ b/displayarea.cpp
@@ -33,7 +33,6 @@
 
 #ifdef Q_OS_LINUX
 #include <QX11Info>
-#include <X11/Xlib.h>
 #endif
 
 
@@ -101,7 +100,7 @@ void DisplayArea::grabKeyboard()
 {
 #ifdef Q_OS_LINUX
     qDebug()<<"Grab X11 Keyboard";
-    XGrabKey(QX11Info::display(), AnyKey, AnyModifier, winId(), false, GrabModeAsync, GrabModeAsync);
+    xcb_grab_key(QX11Info::connection(), 0, winId(), XCB_MOD_MASK_ANY, XCB_GRAB_ANY, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
 #endif
 
 #ifdef Q_OS_WIN
diff --git a/x2gokdriveclient.pro b/x2gokdriveclient.pro
index 339627c..32f89a5 100644
--- a/x2gokdriveclient.pro
+++ b/x2gokdriveclient.pro
@@ -13,7 +13,7 @@ QT += network
 linux {
 message("Building for Linux")
 QT += x11extras
-LIBS += -lX11
+LIBS += -lxcb -lxcb-xfixes
 }
 
 RESOURCES += resources.qrc
@@ -22,3 +22,8 @@ RESOURCES += resources.qrc
 # Input
 SOURCES += main.cpp client.cpp displayarea.cpp menuframe.cpp screenidentifier.cpp
 HEADERS += client.h displayarea.h menuframe.h screenidentifier.h
+
+linux {
+SOURCES += xcbclip.cpp
+HEADERS += xcbclip.h
+}
diff --git a/xcbclip.cpp b/xcbclip.cpp
new file mode 100644
index 0000000..6722ae4
--- /dev/null
+++ b/xcbclip.cpp
@@ -0,0 +1,816 @@
+/*
+ * QT Client for X2GoKDrive
+ * Copyright (C) 2018  Oleksandr Shneyder <o.shneyder at phoca-gmbh.de>
+ * Copyright (C) 2018  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 "xcbclip.h"
+
+#include <QDebug>
+#include <QX11Info>
+#include <QTimer>
+#include <QBuffer>
+
+
+XCBClip::XCBClip(Client* parent)
+{
+
+    this->parent=parent;
+
+    uint32_t             mask = 0;
+    xcb_generic_error_t *error = 0;
+
+    xcb_xfixes_query_version_cookie_t xfixes_query_cookie;
+    xcb_xfixes_query_version_reply_t *xfixes_query;
+    uint32_t             values[2];
+
+
+    con = xcb_connect (NULL, NULL);
+    /* Create the window */
+    xcb_screen_t   *screen = xcb_setup_roots_iterator (xcb_get_setup (con)).data;
+    clipWinId = xcb_generate_id (con);
+    mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
+    values[0] = screen->white_pixel;
+    values[1] = XCB_EVENT_MASK_PROPERTY_CHANGE;
+
+    //create window which will recieve selection events and provide remote selection to X-clients
+    xcb_create_window (con,
+                       XCB_COPY_FROM_PARENT,
+                       clipWinId,
+                       screen->root,
+                       0, 0,
+                       1, 1,
+                       0,
+                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
+                       screen->root_visual,
+                       mask, values);
+
+    xcb_flush(con);
+    //check if we have xfixes, we need it to recieve selection owner events
+
+    reply = xcb_get_extension_data(con, &xcb_xfixes_id);
+    if (reply && reply->present)
+    {
+        xfixes_query_cookie = xcb_xfixes_query_version(con, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);
+        xfixes_query = xcb_xfixes_query_version_reply (con, xfixes_query_cookie, &error);
+        if (!xfixes_query || error || xfixes_query->major_version < 2)
+        {
+            qDebug()<<"XFixes query failed";
+            free(error);
+        }
+        else
+        {
+            //we'll recieve sel owner events for primary amd clipboard
+            mask =  XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER;
+            xcb_xfixes_select_selection_input_checked(con,clipWinId, XCB_ATOM_PRIMARY, mask);
+            xcb_xfixes_select_selection_input_checked(con, clipWinId, atom("CLIPBOARD"), mask);
+        }
+        free(xfixes_query);
+    }
+    xcb_flush(con);
+    QTimer::singleShot(250, this, SLOT(checkEvents()));
+}
+
+
+xcb_atom_t XCBClip::atom(const QString& name)
+{
+    //get atom for the name, return 0 if not found
+    xcb_intern_atom_cookie_t cookie;
+    xcb_intern_atom_reply_t *reply;
+    xcb_atom_t a=0;
+
+    cookie = xcb_intern_atom(con, 0, name.length(), name.toLatin1().data());
+    if ((reply = xcb_intern_atom_reply(con, cookie, NULL)))
+    {
+//         qDebug()<<"found atom for "<<name;
+        a=reply->atom;
+        free(reply);
+    }
+    return a;
+}
+
+bool XCBClip::is_image_atom(xcb_atom_t at)
+{
+    //check if selection data is image
+    if(!at)
+        return false;
+    if( at == atom("image/png") ||
+        at == atom("image/xpm") ||
+        at == atom("image/jpg") ||
+        at == atom("image/jpeg") ||
+        at == atom("PIXMAP") ||
+        at == atom("image/bmp"))
+        return true;
+    return false;
+}
+
+bool XCBClip::is_string_atom(xcb_atom_t at)
+{
+    //check if selection data is string/text
+    if(!at)
+        return false;
+    if( at == atom("UTF8_STRING") ||
+        at == atom("STRING") ||
+        at == atom("TEXT") ||
+        at == atom("text/plain;charset=utf-8") ||
+        at == atom("text/plain"))
+        return true;
+    return false;
+}
+
+QString XCBClip::atom_name(xcb_atom_t xatom)
+{
+    QString name;
+    xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(con, xatom);
+    xcb_get_atom_name_reply_t *reply=xcb_get_atom_name_reply(con, cookie, NULL);
+
+    if(!reply)
+        return name;
+    if(!reply->name_len)
+    {
+        free(reply);
+        return name;
+    }
+    name=QString(QByteArray(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)));
+    free(reply);
+    return name;
+}
+
+QStringList XCBClip::atomsInReply(xcb_get_property_reply_t *reply)
+{
+    QStringList atoms;
+    xcb_atom_t* tg=(xcb_atom_t*) xcb_get_property_value(reply);
+    for(uint i=0;i<xcb_get_property_value_length(reply)/sizeof(xcb_atom_t);++i)
+    {
+        atoms << atom_name(tg[i]);
+    }
+    return atoms;
+}
+
+xcb_atom_t XCBClip::best_atom_from_list(const QStringList& list)
+{
+    //here we chose the best of supported formats for selection
+    xcb_atom_t a;
+    if((a=target_has_atom(list, "UTF8_STRING")))
+    {
+        return a;
+    }
+
+    if((a=target_has_atom(list, "text/plain;charset=utf-8")))
+    {
+        return a;
+    }
+
+    if((a=target_has_atom(list, "STRING")))
+    {
+        return a;
+    }
+    if((a=target_has_atom(list, "TEXT")))
+    {
+        return a;
+    }
+    if((a=target_has_atom(list, "text/plain")))
+    {
+        return a;
+    }
+
+    //Server supports only PNG and JPEG formats
+
+    if((a=target_has_atom(list, "image/png")))
+    {
+         return a;
+    }
+/*    if((a=target_has_atom(list, "image/xpm")))
+    {
+        return a;
+    }
+    if((a=target_has_atom(list, "PIXMAP")))
+    {
+        return a;
+    }
+    if((a=target_has_atom(list, "image/bmp")))
+    {
+        return a;
+    }*/
+    if((a=target_has_atom(list, "image/jpg")))
+    {
+        return a;
+    }
+    if((a=target_has_atom(list, "image/jpeg")))
+    {
+        return a;
+    }
+    return 0;
+}
+
+xcb_atom_t XCBClip::target_has_atom(const QStringList& atoms, const QString& name)
+{
+    foreach (const QString& xatom, atoms)
+    {
+        if(xatom==name)
+        {
+            return atom(name);
+        }
+    }
+    return 0;
+}
+
+void XCBClip::checkEvents()
+{
+    xcb_generic_event_t *e=xcb_poll_for_event(con);
+    if(!e)
+    {
+        //don't have events let*s check again in 100msec
+        QTimer::singleShot(100, this, SLOT(checkEvents()));
+        return;
+    }
+
+    uint response_type=e->response_type & ~0x80;
+
+    //we notified that selection is changed in primary or clipboard
+    if (response_type == reply->first_event + XCB_XFIXES_SELECTION_NOTIFY)
+    {
+        xcb_xfixes_selection_notify_event_t *notify_event=(xcb_xfixes_selection_notify_event_t *)e;
+//         qDebug()<<"SEL OWNER notify, selection:"<<notify_event->selection<< " window "<< notify_event->window<< "owner"<< notify_event->owner;
+        if(notify_event->owner == clipWinId)
+        {
+//             qDebug()<<"not processing, we are the owner of this notify";
+        }
+        else
+        {
+            //cancel all previous incr reading
+            incrementalSize=incrementalSizeRead=0;
+            incrAtom=0;
+            if(notify_event->selection==XCB_ATOM_PRIMARY)
+            {
+                owner[0]=false;
+            }
+            else
+            {
+                owner[1]=false;
+            }
+            //get supported mime types
+            request_selection_data( notify_event->selection, atom( "TARGETS"), atom( "TARGETS"), 0);
+        }
+    }
+    else
+    {
+        //we notified that property is changed
+        if (response_type == XCB_PROPERTY_NOTIFY)
+        {
+            process_property_notify(e);
+        }
+        //we got reply to our selection request (mime types or data)
+        else if (response_type == XCB_SELECTION_NOTIFY)
+        {
+            process_selection_notify(e);
+        }
+        else if (response_type == XCB_SELECTION_REQUEST)
+        {
+            process_selection_request(e);
+        }
+        else
+        {
+//             qDebug()<<"not processing this event "<<response_type;
+        }
+    }
+
+    free(e);
+
+    //return to the main eventsloop before processing new events, selection has less priority than other events
+    QTimer::singleShot(10, this, SLOT(checkEvents()));
+}
+
+
+
+
+void XCBClip::process_selection_notify(xcb_generic_event_t *e)
+{
+    xcb_selection_notify_event_t *sel_event;
+
+//     qDebug()<<"selection notify";
+    sel_event=(xcb_selection_notify_event_t *)e;
+
+    //processing the event which is reply for convert selection call
+
+
+    if (sel_event->requestor != clipWinId)
+    {
+//         qDebug()<<("not our window");
+        return;
+    }
+    else
+    {
+//         qDebug()<<"selection notify sel , target , property "<< sel_event->selection<< sel_event->target<< sel_event->property;
+        if(sel_event->property==XCB_NONE)
+        {
+            qDebug()<<( "NO SELECTION");
+        }
+        else
+        {
+
+            currentSelection=sel_event->selection;
+            //read property
+            read_selection_property(currentSelection, sel_event->property);
+        }
+    }
+}
+
+
+void XCBClip::process_property_notify(xcb_generic_event_t *e)
+{
+    xcb_property_notify_event_t *pn;
+
+//     qDebug()<<("property notify");
+
+    pn = (xcb_property_notify_event_t *)e;
+    if (pn->window != clipWinId)
+    {
+//         qDebug()<<("not our window");
+        return;
+    }
+//     qDebug()<<"property, state "<< pn->atom<< pn->state;
+    if(pn->state==XCB_PROPERTY_NEW_VALUE)
+    {
+        if(incrAtom==pn->atom && incrementalSize)
+        {
+            //we recieveing the selection data incrementally, let's read a next chunk
+//             qDebug()<<"reading incr property "<< pn->atom;
+            read_selection_property(currentSelection, pn->atom);
+        }
+    }
+}
+
+void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property)
+{
+    QString stype, sprop;
+    xcb_atom_t data_atom;
+    unsigned int bytes_left, bytes_read=0;
+    xcb_get_property_cookie_t cookie;
+    xcb_get_property_reply_t *reply;
+    OutputChunk* chunk;
+
+
+    //request property which represents value of selection (data or mime types)
+    //get max 100K of data, we don't need to send more than that over network for perfomance reasons
+    cookie= xcb_get_property(con,false, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, 0, max_chunk());
+    reply=xcb_get_property_reply(con, cookie, NULL);
+    if(!reply)
+    {
+        qDebug()<<( "NULL reply");
+    }
+    else
+    {
+        if(reply->type==XCB_NONE)
+        {
+            qDebug()<<( "NONE reply");
+        }
+        else
+        {
+            //here we have type of data
+            stype=atom_name(reply->type);
+            sprop=atom_name(property);
+
+//             qDebug()<< "Property, type, format, length"<< sprop<< stype<< reply->format<< reply->length;
+            //need to read property incrementally
+            if(reply->type == atom("INCR"))
+            {
+                unsigned int sz=*((unsigned int*) xcb_get_property_value(reply));
+//                 qDebug()<< "have incr property size: "<< sz;
+                incrAtom=property;
+                incrementalSize=sz;
+                incrementalSizeRead=0;
+
+                //deleteing property should tell the selection owner that we are ready for incremental reading of data
+                xcb_delete_property(con, clipWinId, property);
+                xcb_flush(con);
+                free(reply);
+                return;
+            }
+            //we have supported mime types in reply
+            if(reply->type == atom( "ATOM"))
+            {
+                if(reply->format!=32)
+                {
+                    qDebug()<<( "wrong format for TARGETS");
+                }
+                else
+                {
+                    QStringList atoms=atomsInReply(reply);
+//                     qDebug() << "target supports mime types:"<<atoms;
+
+                    if(parent->serverSupportsExtSelection())
+                    {
+                        //servere support extended selection, we'll send the selection data first when it's requested by the server
+                        //now we'll just notify the server that there is the new selection and send supported mime types
+                    }
+                    else
+                    {
+                        data_atom=0;
+                        //get the best of supported mime types and request the selection in this format
+                        data_atom=best_atom_from_list(atoms);
+
+                        xcb_delete_property( con, clipWinId, property);
+                        xcb_flush(con);
+
+                        //request the data from selection
+                        if(data_atom)
+                            request_selection_data( selection, data_atom, data_atom, 0);
+                        else
+                        {
+                            qDebug()<<( "there are no supported mime types in the target");
+                        }
+                    }
+                }
+            }
+            else
+            {
+                //here we have selection as string or image
+                if(is_image_atom( reply->type) || is_string_atom( reply->type))
+                {
+                    //read property data in loop in the chunks with size (100KB)
+                    do
+                    {
+                        bytes_left=reply->bytes_after;
+                        //now we can access property data
+
+                        /*FILE* cp=fopen("/tmp/clip", "a");
+                         *                        fwrite(xcb_get_property_value(reply),1, xcb_get_property_value_length(reply),cp);
+                         *                        fclose(cp);*/
+
+
+
+                        SelectionType sel= CLIPBOARD;
+                        if(selection==XCB_ATOM_PRIMARY)
+                            sel=PRIMARY;
+
+                        SelectionMime mime= UTF_STRING;
+                        if(is_image_atom(reply->type))
+                            mime=PIXMAP;
+
+                        chunk=new OutputChunk(sel, mime);
+
+
+                        if(xcb_get_property_value_length(reply))
+                        {
+                            chunk->data.setRawData((const char*) xcb_get_property_value(reply),xcb_get_property_value_length(reply));
+                        }
+
+                        chunk->compressed=false;
+                        if(is_string_atom(property))
+                            chunk->mimeData=UTF_STRING;
+                        else
+                            chunk->mimeData=PIXMAP;
+
+                        if(selection == XCB_ATOM_PRIMARY)
+                        {
+                            chunk->selection=PRIMARY;
+                        }
+                        else
+                            chunk->selection=CLIPBOARD;
+
+
+                        if(incrementalSize && (incrAtom==property))
+                        {
+                            //we are doing incremental reading
+                            if(incrementalSizeRead == 0)
+                            {
+                                //it's the first chunk
+                                chunk->firstChunk=true;
+                            }
+                            incrementalSizeRead+=xcb_get_property_value_length(reply);
+                            if(!bytes_left && ! bytes_read && !xcb_get_property_value_length(reply))
+                            {
+                                //we got the property with 0 size it means that we recieved all data of incr property
+//                                 qDebug()<<"INCR Property done, read  " << incrementalSizeRead;
+                                incrAtom=0;
+                                incrementalSize=0;
+                                //it's the last chunk
+                                chunk->lastChunk=true;
+                            }
+                            chunk->totalSize=incrementalSize;
+                        }
+                        else
+                        {
+                            //we are doing simple read
+                            if(bytes_read==0)
+                            {
+                                //it's the first chunk
+                                chunk->firstChunk=true;
+                            }
+                            if(bytes_left==0)
+                            {
+                                //the last chunk
+                                chunk->lastChunk=true;
+                            }
+                            chunk->totalSize=xcb_get_property_value_length(reply)+bytes_left;
+                        }
+
+                        bytes_read+=xcb_get_property_value_length(reply);
+//                         qDebug()<<"read chunk of selection - size, total read, left, first:, last:"<< xcb_get_property_value_length(reply)<< bytes_read<< bytes_left<< chunk->firstChunk<< chunk->lastChunk;
+
+                        //attach chunk to the end of output chunk queue
+                        parent-> addToSelectionOutput(chunk);
+
+                        //send this chunk
+                        parent->sendOutputSelChunk();
+
+                        if(bytes_left)
+                        {
+                            free(reply);
+                            cookie= xcb_get_property(con, 0, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, bytes_read/4,max_chunk());
+                            reply=xcb_get_property_reply(con, cookie, NULL);
+                            if(!reply)
+                            {
+                                //something is wrong
+                                qDebug()<<("NULL reply");
+                                break;
+                            }
+                        }
+                        //read in loop till no data left
+                    }while(bytes_left);
+                }
+                else
+                {
+                    stype=atom_name(reply->type);
+                    qDebug()<<"Not supported mime type: "<<stype << reply->type;
+                }
+            }
+            if(reply)
+                free(reply);
+            //if reading incr property this will say sel owner that we are ready for the next chunk of data
+            xcb_delete_property(con, clipWinId, property);
+            xcb_flush(con);
+        }
+    }
+}
+
+SelectionType XCBClip::selection_from_atom(xcb_atom_t selection)
+{
+    if(selection == XCB_ATOM_PRIMARY)
+        return PRIMARY;
+    return CLIPBOARD;
+
+}
+
+void  XCBClip::request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t)
+{
+    //execute convert selection for primary or clipboard to get mimetypes or data (depends on target atom)
+    if(!t)
+        t=XCB_CURRENT_TIME;
+    if(property)
+    {
+        xcb_delete_property(con,clipWinId,property);
+    }
+    xcb_convert_selection(con, clipWinId,selection, target, property, t);
+    xcb_flush(con);
+}
+
+
+void XCBClip::setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, bool compressed, uint size, char* data)
+{
+    //copy data to selection buffer
+//     qDebug()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size;
+
+
+    if(firstChunk)
+    {
+        selData[selection].clear();
+        selMime[selection]=mime;
+    }
+
+    selData[selection].append(data,size);
+
+    if(lastChunk)
+    {
+        own_selection(selection);
+    }
+}
+
+void XCBClip::own_selection(SelectionType selection)
+{
+    xcb_atom_t sel=XCB_ATOM_PRIMARY;
+    if(selection!=PRIMARY)
+    {
+        sel=atom("CLIPBOARD");
+    }
+    xcb_set_selection_owner(con, clipWinId, sel, XCB_CURRENT_TIME);
+    xcb_flush(con);
+    owner[selection]=true;
+    timestamp[selection]=XCB_CURRENT_TIME;
+}
+
+void XCBClip::process_selection_request(xcb_generic_event_t *e)
+{
+    xcb_selection_request_event_t *req=(xcb_selection_request_event_t*)e;
+
+    xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1);
+    event->response_type = XCB_SELECTION_NOTIFY;
+    event->requestor = req->requestor;
+    event->selection = req->selection;
+    event->target    = req->target;
+    event->property  = XCB_NONE;
+    event->time      = req->time;
+
+    xcb_atom_t property=req->property;
+    xcb_atom_t target=req->target;
+
+    if(property == XCB_NONE)
+        property=target;
+
+    SelectionType sel=selection_from_atom(req->selection);
+
+//     qDebug()<<"selection request for"<<atom_name(req->selection)<<atom_name(req->target)<<atom_name(req->property);
+    if(!owner[sel])
+    {
+        //we don't own this selection
+        qDebug()<<"not our selection";
+        xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
+        xcb_flush(con);
+        free(event);
+        return;
+    }
+
+    if(timestamp[sel] > req->time)
+    {
+        //selection changed after request
+        qDebug()<<"requested selection doesn't exist anymore";
+        xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
+        xcb_flush(con);
+        free(event);
+        return;
+    }
+
+
+    if(req->target==atom("TIMESTAMP"))
+    {
+        event->property=property;
+//         qDebug()<<"requested TIMESTAMP";
+        xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor,
+                                property, XCB_ATOM_INTEGER, 32, 1, &timestamp[sel]);
+
+    }
+    else if(req->target==atom("TARGETS"))
+    {
+        event->property=property;
+//         qDebug()<<"requested TARGETS";
+        send_mime_types(req);
+    }
+    else
+    {
+        event->property=send_data(req);
+    }
+    xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
+    xcb_flush(con);
+    free(event);
+}
+
+QString XCBClip::mime_to_QT_img(const QString& mimeType)
+{
+    //convert mimeType to internal QT image format:
+    //https://doc.qt.io/qt-5/qimage.html#reading-and-writing-image-files
+    QString f=mimeType;
+    if(mimeType=="PIXMAP")
+        return "XPM";
+    f=f.replace("image/","").toUpper();
+    return f;
+}
+
+
+xcb_atom_t XCBClip::set_data_property(xcb_selection_request_event_t* req, QByteArray* data)
+{
+
+    //set data to window property
+
+    //change when implemented
+    bool support_incr=false;
+    //this types of application not supporting incr selection
+    if(atom_name(req->property)=="_XT_SELECTION_0" || atom_name(req->property)=="_QT_SELECTION")
+    {
+        qDebug()<<atom_name(req->property)<<"doesn't support INCR";
+        support_incr=false;
+    }
+//     qDebug()<<"check if data size < "<<xcb_get_maximum_request_length(con) * 4 - 24;
+
+    //check if we are sending incr
+    if(data->size() < (int)xcb_get_maximum_request_length(con) * 4 - 24)
+    {
+//         qDebug()<<"sending "<<data->size()<<atom_name(req->property)<<atom_name(req->target);
+
+        xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target,
+                            8, data->size(), (const void *)data->constData());
+
+        xcb_flush(con);
+        return req->property;
+    }
+
+    qDebug()<<"data is too big";
+    return XCB_NONE;
+}
+
+
+xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req)
+{
+
+    //send data
+    SelectionType sel=selection_from_atom(req->selection);
+
+    if(selMime[sel]==UTF_STRING)
+    {
+        //if it's one of supported text formats send without convertion
+        if(is_string_atom(req->target))
+        {
+//             qDebug()<<"sending UTF text";
+            return set_data_property(req, &selData[sel]);
+        }
+        else
+        {
+            qDebug()<<"unsupported property requested:"<<atom_name(req->target);
+            return XCB_NONE;
+        }
+    }
+    else
+    {
+        if(!is_image_atom(req->target))
+        {
+            qDebug()<<"unsupported property requested:"<<atom_name(req->target);
+            return XCB_NONE;
+        }
+//         qDebug()<<"sending "<<atom_name(req->target);
+        //convert to desireable format
+        QImage img=QImage::fromData(selData[sel]);
+        QByteArray ba;
+        QBuffer buffer(&ba);
+        buffer.open(QIODevice::WriteOnly);
+        img.save(&buffer, mime_to_QT_img(atom_name(req->target)).toLatin1());
+//         qDebug()<<"converted to"<<mime_to_QT_img(atom_name(req->target))<<ba.size();
+        return set_data_property(req, &ba);
+    }
+    return XCB_NONE;
+}
+
+void XCBClip::send_mime_types(xcb_selection_request_event_t* req)
+{
+    //send supported targets
+    SelectionType sel=selection_from_atom(req->selection);
+    QVector<xcb_atom_t> targets;
+    xcb_atom_t a;
+
+    if((a=atom("TARGETS")))
+        targets.append(a);
+    if((a=atom("TIMESTAMP")))
+        targets.append(a);
+
+    if(selMime[sel]==PIXMAP)
+    {
+        if((a=atom("image/png")))
+            targets.append(a);
+        if((a=atom("image/jpg")))
+            targets.append(a);
+        if((a=atom("image/jpeg")))
+            targets.append(a);
+        if((a=atom("image/bmp")))
+            targets.append(a);
+        if((a=atom("image/xpm")))
+            targets.append(a);
+    }
+    else
+    {
+        if((a=atom("UTF8_STRING")))
+            targets.append(a);
+        if((a=atom("text/plain;charset=utf-8")))
+            targets.append(a);
+        if((a=atom("STRING")))
+            targets.append(a);
+        if((a=atom("TEXT")))
+            targets.append(a);
+        if((a=atom("text/plain")))
+            targets.append(a);
+    }
+
+    xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, XCB_ATOM_ATOM,
+                        32, targets.size(), (const void *)targets.constData());
+    xcb_flush(con);
+
+}
+
+uint XCBClip::max_chunk()
+{
+    if(parent->serverSupportsExtSelection())
+        return 1024*100/4; //100KB
+        else
+            return 10*1024*1024/4; //10MB
+}
diff --git a/xcbclip.h b/xcbclip.h
new file mode 100644
index 0000000..c45fc91
--- /dev/null
+++ b/xcbclip.h
@@ -0,0 +1,79 @@
+/*
+ * QT Client for X2GoKDrive
+ * Copyright (C) 2018  Oleksandr Shneyder <o.shneyder at phoca-gmbh.de>
+ * Copyright (C) 2018  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/>.
+ *
+ */
+
+#ifndef XCBCLIP_H
+#define XCBCLIP_H
+#include <QMainWindow>
+#include <QClipboard>
+
+#include <xcb/xcb.h>
+#include <xcb/xfixes.h>
+#include <QThread>
+
+#include "client.h"
+
+class XCBClip : public QObject
+{
+    Q_OBJECT
+public:
+    XCBClip(Client* parent);
+    void setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, bool compressed, uint size, char* data);
+
+private:
+
+    uint max_chunk(void);
+    xcb_atom_t atom(const QString& name);
+    QString atom_name(xcb_atom_t xatom);
+    QStringList atomsInReply(xcb_get_property_reply_t *reply);
+    xcb_atom_t best_atom_from_list(const QStringList& atoms);
+    void process_selection_notify(xcb_generic_event_t *e);
+    void process_selection_request(xcb_generic_event_t *e);
+    void request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t);
+    void process_property_notify(xcb_generic_event_t *e);
+    void read_selection_property(xcb_atom_t selection, xcb_atom_t property);
+    bool is_string_atom( xcb_atom_t at);
+    bool is_image_atom( xcb_atom_t at);
+    xcb_atom_t target_has_atom(const QStringList& atoms, const QString& name);
+    void own_selection(SelectionType selection);
+    void send_mime_types(xcb_selection_request_event_t* req);
+    xcb_atom_t send_data(xcb_selection_request_event_t* req);
+    xcb_atom_t set_data_property(xcb_selection_request_event_t* req, QByteArray* data);
+    SelectionType selection_from_atom(xcb_atom_t selection);
+    QString mime_to_QT_img(const QString& mimeType);
+
+private slots:
+    void checkEvents();
+private:
+    Client* parent;
+    const xcb_query_extension_reply_t *reply;
+    xcb_window_t clipWinId;
+    xcb_connection_t* con;
+//output selection
+    xcb_atom_t currentSelection;
+    xcb_atom_t incrAtom=0;
+    uint incrementalSize=0;
+    uint incrementalSizeRead=0;
+//input selection
+    SelectionMime selMime[2];
+    QByteArray selData[2];
+    bool owner[2]={0};
+    xcb_timestamp_t timestamp[2];
+};
+#endif

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