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
47QT_BEGIN_NAMESPACE
48
49QXcbMime::QXcbMime()
50 : QInternalMimeData()
51{ }
52
53QXcbMime::~QXcbMime()
54{}
55
56
57
58QString 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
82bool 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
133QVector<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
161QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &d, const QString &format,
162 QVariant::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 == QVariant::String) {
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
267xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString &format, QVariant::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 == QVariant::String
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
321QT_END_NAMESPACE
322