[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, ×tamp[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