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 | |
49 | QXcbMime::QXcbMime() |
50 | : QInternalMimeData() |
51 | { } |
52 | |
53 | QXcbMime::~QXcbMime() |
54 | {} |
55 | |
56 | |
57 | |
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(qatom: QXcbAtom::UTF8_STRING) |
66 | || a == connection->atom(qatom: 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(atom: a); |
74 | |
75 | // special cases for uris |
76 | if (atomName == "text/x-moz-url" ) |
77 | atomName = "text/uri-list" ; |
78 | |
79 | return QString::fromLatin1(str: atomName.constData()); |
80 | } |
81 | |
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(qatom: QXcbAtom::UTF8_STRING) |
93 | || a == XCB_ATOM_STRING |
94 | || a == connection->atom(qatom: QXcbAtom::TEXT)) |
95 | && QInternalMimeData::hasFormatHelper(mimeType: QLatin1String("text/plain" ), data: mimeData)) { |
96 | if (a == connection->atom(qatom: QXcbAtom::UTF8_STRING)) { |
97 | *data = QInternalMimeData::renderDataHelper(mimeType: QLatin1String("text/plain" ), data: mimeData); |
98 | ret = true; |
99 | } else if (a == XCB_ATOM_STRING || |
100 | a == connection->atom(qatom: QXcbAtom::TEXT)) { |
101 | // ICCCM says STRING is latin1 |
102 | *data = QString::fromUtf8(str: QInternalMimeData::renderDataHelper( |
103 | mimeType: QLatin1String("text/plain" ), data: mimeData)).toLatin1(); |
104 | ret = true; |
105 | } |
106 | return ret; |
107 | } |
108 | |
109 | QString atomName = mimeAtomToString(connection, a); |
110 | if (QInternalMimeData::hasFormatHelper(mimeType: atomName, data: mimeData)) { |
111 | *data = QInternalMimeData::renderDataHelper(mimeType: atomName, data: 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(atom: a) == "text/x-moz-url" ) { |
116 | const QString mozUri = QLatin1String(data->split(sep: '\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(mimetype: QLatin1String("text/uri-list" ))) { |
126 | // Return URLs also as plain text. |
127 | *data = QInternalMimeData::renderDataHelper(mimeType: atomName, data: mimeData); |
128 | ret = true; |
129 | } |
130 | return ret; |
131 | } |
132 | |
133 | QVector<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, const QString &format) |
134 | { |
135 | QVector<xcb_atom_t> atoms; |
136 | atoms.reserve(asize: 7); |
137 | atoms.append(t: connection->internAtom(name: format.toLatin1())); |
138 | |
139 | // special cases for strings |
140 | if (format == QLatin1String("text/plain" )) { |
141 | atoms.append(t: connection->atom(qatom: QXcbAtom::UTF8_STRING)); |
142 | atoms.append(t: XCB_ATOM_STRING); |
143 | atoms.append(t: connection->atom(qatom: QXcbAtom::TEXT)); |
144 | } |
145 | |
146 | // special cases for uris |
147 | if (format == QLatin1String("text/uri-list" )) { |
148 | atoms.append(t: connection->internAtom(name: "text/x-moz-url" )); |
149 | atoms.append(t: connection->internAtom(name: "text/plain" )); |
150 | } |
151 | |
152 | //special cases for images |
153 | if (format == QLatin1String("image/ppm" )) |
154 | atoms.append(t: XCB_ATOM_PIXMAP); |
155 | if (format == QLatin1String("image/pbm" )) |
156 | atoms.append(t: XCB_ATOM_BITMAP); |
157 | |
158 | return atoms; |
159 | } |
160 | |
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(name: 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(c: '\0')) |
185 | data.chop(n: 1); |
186 | if (a == connection->atom(qatom: QXcbAtom::UTF8_STRING)) { |
187 | return QString::fromUtf8(str: data); |
188 | } |
189 | if (a == XCB_ATOM_STRING || |
190 | a == connection->atom(qatom: QXcbAtom::TEXT)) |
191 | return QString::fromLatin1(str: 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(i: 0); |
200 | const quint8 byte1 = data.at(i: 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()), size: data.size() / 2); |
205 | if (!str.isNull()) { |
206 | if (format == QLatin1String("text/uri-list" )) { |
207 | const auto urls = str.splitRef(sep: QLatin1Char('\n')); |
208 | QList<QVariant> list; |
209 | list.reserve(alloc: urls.size()); |
210 | for (const QStringRef &s : urls) { |
211 | const QUrl url(s.trimmed().toString()); |
212 | if (url.isValid()) |
213 | list.append(t: 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(atom: 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(c: '\0')) |
228 | data.chop(n: 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 | |
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(t: connection->atom(qatom: QXcbAtom::UTF8_STRING))) |
275 | return connection->atom(qatom: QXcbAtom::UTF8_STRING); |
276 | if (atoms.contains(t: XCB_ATOM_STRING)) |
277 | return XCB_ATOM_STRING; |
278 | if (atoms.contains(t: connection->atom(qatom: QXcbAtom::TEXT))) |
279 | return connection->atom(qatom: QXcbAtom::TEXT); |
280 | } |
281 | |
282 | // find matches for uri types |
283 | if (format == QLatin1String("text/uri-list" )) { |
284 | xcb_atom_t a = connection->internAtom(name: format.toLatin1()); |
285 | if (a && atoms.contains(t: a)) |
286 | return a; |
287 | a = connection->internAtom(name: "text/x-moz-url" ); |
288 | if (a && atoms.contains(t: a)) |
289 | return a; |
290 | } |
291 | |
292 | // find match for image |
293 | if (format == QLatin1String("image/ppm" )) { |
294 | if (atoms.contains(t: 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(s: QLatin1String("text/" )) |
302 | && !format.contains(s: QLatin1String("charset=" ))) { |
303 | |
304 | QString formatWithCharset = format; |
305 | formatWithCharset.append(s: QLatin1String(";charset=utf-8" )); |
306 | |
307 | xcb_atom_t a = connection->internAtom(name: std::move(formatWithCharset).toLatin1()); |
308 | if (a && atoms.contains(t: a)) { |
309 | *requestedEncoding = "utf-8" ; |
310 | return a; |
311 | } |
312 | } |
313 | |
314 | xcb_atom_t a = connection->internAtom(name: format.toLatin1()); |
315 | if (a && atoms.contains(t: a)) |
316 | return a; |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | QT_END_NAMESPACE |
322 | |