1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qxcbmime.h"
41 
42 #include <QtCore/QTextCodec>
43 #include <QtGui/QImageWriter>
44 #include <QtCore/QBuffer>
45 #include <qdebug.h>
46 
47 QT_BEGIN_NAMESPACE
48 
QXcbMime()49 QXcbMime::QXcbMime()
50     : QInternalMimeData()
51 { }
52 
~QXcbMime()53 QXcbMime::~QXcbMime()
54 {}
55 
56 
57 
mimeAtomToString(QXcbConnection * connection,xcb_atom_t a)58 QString QXcbMime::mimeAtomToString(QXcbConnection *connection, xcb_atom_t a)
59 {
60     if (a == XCB_NONE)
61         return QString();
62 
63     // special cases for string type
64     if (a == XCB_ATOM_STRING
65         || a == connection->atom(QXcbAtom::UTF8_STRING)
66         || a == connection->atom(QXcbAtom::TEXT))
67         return QLatin1String("text/plain");
68 
69     // special case for images
70     if (a == XCB_ATOM_PIXMAP)
71         return QLatin1String("image/ppm");
72 
73     QByteArray atomName = connection->atomName(a);
74 
75     // special cases for uris
76     if (atomName == "text/x-moz-url")
77         atomName = "text/uri-list";
78 
79     return QString::fromLatin1(atomName.constData());
80 }
81 
mimeDataForAtom(QXcbConnection * connection,xcb_atom_t a,QMimeData * mimeData,QByteArray * data,xcb_atom_t * atomFormat,int * dataFormat)82 bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeData *mimeData, QByteArray *data,
83                                xcb_atom_t *atomFormat, int *dataFormat)
84 {
85     if (!data)
86         return false;
87 
88     bool ret = false;
89     *atomFormat = a;
90     *dataFormat = 8;
91 
92     if ((a == connection->atom(QXcbAtom::UTF8_STRING)
93          || a == XCB_ATOM_STRING
94          || a == connection->atom(QXcbAtom::TEXT))
95         && QInternalMimeData::hasFormatHelper(QLatin1String("text/plain"), mimeData)) {
96         if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
97             *data = QInternalMimeData::renderDataHelper(QLatin1String("text/plain"), mimeData);
98             ret = true;
99         } else if (a == XCB_ATOM_STRING ||
100                    a == connection->atom(QXcbAtom::TEXT)) {
101             // ICCCM says STRING is latin1
102             *data = QString::fromUtf8(QInternalMimeData::renderDataHelper(
103                         QLatin1String("text/plain"), mimeData)).toLatin1();
104             ret = true;
105         }
106         return ret;
107     }
108 
109     QString atomName = mimeAtomToString(connection, a);
110     if (QInternalMimeData::hasFormatHelper(atomName, mimeData)) {
111         *data = QInternalMimeData::renderDataHelper(atomName, mimeData);
112         // mimeAtomToString() converts "text/x-moz-url" to "text/uri-list",
113         // so QXcbConnection::atomName() has to be used.
114         if (atomName == QLatin1String("text/uri-list")
115             && connection->atomName(a) == "text/x-moz-url") {
116             const QString mozUri = QLatin1String(data->split('\n').constFirst()) + QLatin1Char('\n');
117             *data = QByteArray(reinterpret_cast<const char *>(mozUri.utf16()),
118                                mozUri.length() * 2);
119         } else if (atomName == QLatin1String("application/x-color"))
120             *dataFormat = 16;
121         ret = true;
122     } else if ((a == XCB_ATOM_PIXMAP || a == XCB_ATOM_BITMAP) && mimeData->hasImage()) {
123         ret = true;
124     } else if (atomName == QLatin1String("text/plain")
125                && mimeData->hasFormat(QLatin1String("text/uri-list"))) {
126         // Return URLs also as plain text.
127         *data = QInternalMimeData::renderDataHelper(atomName, mimeData);
128         ret = true;
129     }
130     return ret;
131 }
132 
mimeAtomsForFormat(QXcbConnection * connection,const QString & format)133 QVector<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, const QString &format)
134 {
135     QVector<xcb_atom_t> atoms;
136     atoms.reserve(7);
137     atoms.append(connection->internAtom(format.toLatin1()));
138 
139     // special cases for strings
140     if (format == QLatin1String("text/plain")) {
141         atoms.append(connection->atom(QXcbAtom::UTF8_STRING));
142         atoms.append(XCB_ATOM_STRING);
143         atoms.append(connection->atom(QXcbAtom::TEXT));
144     }
145 
146     // special cases for uris
147     if (format == QLatin1String("text/uri-list")) {
148         atoms.append(connection->internAtom("text/x-moz-url"));
149         atoms.append(connection->internAtom("text/plain"));
150     }
151 
152     //special cases for images
153     if (format == QLatin1String("image/ppm"))
154         atoms.append(XCB_ATOM_PIXMAP);
155     if (format == QLatin1String("image/pbm"))
156         atoms.append(XCB_ATOM_BITMAP);
157 
158     return atoms;
159 }
160 
mimeConvertToFormat(QXcbConnection * connection,xcb_atom_t a,const QByteArray & d,const QString & format,QMetaType::Type requestedType,const QByteArray & encoding)161 QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &d, const QString &format,
162                                        QMetaType::Type requestedType, const QByteArray &encoding)
163 {
164     QByteArray data = d;
165     QString atomName = mimeAtomToString(connection, a);
166 //    qDebug() << "mimeConvertDataToFormat" << format << atomName << data;
167 
168     if (!encoding.isEmpty()
169         && atomName == format + QLatin1String(";charset=") + QLatin1String(encoding)) {
170 
171 #if QT_CONFIG(textcodec)
172         if (requestedType == QMetaType::QString) {
173             QTextCodec *codec = QTextCodec::codecForName(encoding);
174             if (codec)
175                 return codec->toUnicode(data);
176         }
177 #endif
178 
179         return data;
180     }
181 
182     // special cases for string types
183     if (format == QLatin1String("text/plain")) {
184         if (data.endsWith('\0'))
185             data.chop(1);
186         if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
187             return QString::fromUtf8(data);
188         }
189         if (a == XCB_ATOM_STRING ||
190             a == connection->atom(QXcbAtom::TEXT))
191             return QString::fromLatin1(data);
192     }
193     // If data contains UTF16 text, convert it to a string.
194     // Firefox uses UTF16 without BOM for text/x-moz-url, "text/html",
195     // Google Chrome uses UTF16 without BOM for "text/x-moz-url",
196     // UTF16 with BOM for "text/html".
197     if ((format == QLatin1String("text/html") || format == QLatin1String("text/uri-list"))
198         && data.size() > 1) {
199         const quint8 byte0 = data.at(0);
200         const quint8 byte1 = data.at(1);
201         if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff)
202             || (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) {
203             const QString str = QString::fromUtf16(
204                   reinterpret_cast<const ushort *>(data.constData()), data.size() / 2);
205             if (!str.isNull()) {
206                 if (format == QLatin1String("text/uri-list")) {
207                     const auto urls = str.splitRef(QLatin1Char('\n'));
208                     QList<QVariant> list;
209                     list.reserve(urls.size());
210                     for (const QStringRef &s : urls) {
211                         const QUrl url(s.trimmed().toString());
212                         if (url.isValid())
213                             list.append(url);
214                     }
215                     // We expect "text/x-moz-url" as <url><space><title>.
216                     // The atomName variable is not used because mimeAtomToString()
217                     // converts "text/x-moz-url" to "text/uri-list".
218                     if (!list.isEmpty() && connection->atomName(a) == "text/x-moz-url")
219                         return list.constFirst();
220                     return list;
221                 } else {
222                     return str;
223                 }
224             }
225         }
226         // 8 byte encoding, remove a possible 0 at the end
227         if (data.endsWith('\0'))
228             data.chop(1);
229     }
230 
231     if (atomName == format)
232         return data;
233 
234 #if 0 // ###
235     // special case for images
236     if (format == QLatin1String("image/ppm")) {
237         if (a == XCB_ATOM_PIXMAP && data.size() == sizeof(Pixmap)) {
238             Pixmap xpm = *((Pixmap*)data.data());
239             if (!xpm)
240                 return QByteArray();
241             Window root;
242             int x;
243             int y;
244             uint width;
245             uint height;
246             uint border_width;
247             uint depth;
248 
249             XGetGeometry(display, xpm, &root, &x, &y, &width, &height, &border_width, &depth);
250             XImage *ximg = XGetImage(display,xpm,x,y,width,height,AllPlanes,depth==1 ? XYPixmap : ZPixmap);
251             QImage qimg = QXlibStatic::qimageFromXImage(ximg);
252             XDestroyImage(ximg);
253 
254             QImageWriter imageWriter;
255             imageWriter.setFormat("PPMRAW");
256             QBuffer buf;
257             buf.open(QIODevice::WriteOnly);
258             imageWriter.setDevice(&buf);
259             imageWriter.write(qimg);
260             return buf.buffer();
261         }
262     }
263 #endif
264     return QVariant();
265 }
266 
mimeAtomForFormat(QXcbConnection * connection,const QString & format,QMetaType::Type requestedType,const QVector<xcb_atom_t> & atoms,QByteArray * requestedEncoding)267 xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString &format, QMetaType::Type requestedType,
268                                  const QVector<xcb_atom_t> &atoms, QByteArray *requestedEncoding)
269 {
270     requestedEncoding->clear();
271 
272     // find matches for string types
273     if (format == QLatin1String("text/plain")) {
274         if (atoms.contains(connection->atom(QXcbAtom::UTF8_STRING)))
275             return connection->atom(QXcbAtom::UTF8_STRING);
276         if (atoms.contains(XCB_ATOM_STRING))
277             return XCB_ATOM_STRING;
278         if (atoms.contains(connection->atom(QXcbAtom::TEXT)))
279             return connection->atom(QXcbAtom::TEXT);
280     }
281 
282     // find matches for uri types
283     if (format == QLatin1String("text/uri-list")) {
284         xcb_atom_t a = connection->internAtom(format.toLatin1());
285         if (a && atoms.contains(a))
286             return a;
287         a = connection->internAtom("text/x-moz-url");
288         if (a && atoms.contains(a))
289             return a;
290     }
291 
292     // find match for image
293     if (format == QLatin1String("image/ppm")) {
294         if (atoms.contains(XCB_ATOM_PIXMAP))
295             return XCB_ATOM_PIXMAP;
296     }
297 
298     // for string/text requests try to use a format with a well-defined charset
299     // first to avoid encoding problems
300     if (requestedType == QMetaType::QString
301         && format.startsWith(QLatin1String("text/"))
302         && !format.contains(QLatin1String("charset="))) {
303 
304         QString formatWithCharset = format;
305         formatWithCharset.append(QLatin1String(";charset=utf-8"));
306 
307         xcb_atom_t a = connection->internAtom(std::move(formatWithCharset).toLatin1());
308         if (a && atoms.contains(a)) {
309             *requestedEncoding = "utf-8";
310             return a;
311         }
312     }
313 
314     xcb_atom_t a = connection->internAtom(format.toLatin1());
315     if (a && atoms.contains(a))
316         return a;
317 
318     return 0;
319 }
320 
321 QT_END_NAMESPACE
322