1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qxcbmime.h" |
5 | |
6 | #include <QtGui/QImageWriter> |
7 | #include <QtCore/QBuffer> |
8 | #include <qdebug.h> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | using namespace Qt::StringLiterals; |
13 | |
14 | QXcbMime::QXcbMime() |
15 | : QInternalMimeData() |
16 | { } |
17 | |
18 | QXcbMime::~QXcbMime() |
19 | {} |
20 | |
21 | |
22 | |
23 | QString QXcbMime::mimeAtomToString(QXcbConnection *connection, xcb_atom_t a) |
24 | { |
25 | if (a == XCB_NONE) |
26 | return QString(); |
27 | |
28 | // special cases for string type |
29 | if (a == XCB_ATOM_STRING |
30 | || a == connection->atom(qatom: QXcbAtom::AtomUTF8_STRING) |
31 | || a == connection->atom(qatom: QXcbAtom::AtomTEXT)) |
32 | return "text/plain"_L1 ; |
33 | |
34 | // special case for images |
35 | if (a == XCB_ATOM_PIXMAP) |
36 | return "image/ppm"_L1 ; |
37 | |
38 | QByteArray atomName = connection->atomName(atom: a); |
39 | |
40 | // special cases for uris |
41 | if (atomName == "text/x-moz-url" ) |
42 | atomName = "text/uri-list" ; |
43 | |
44 | return QString::fromLatin1(ba: atomName.constData()); |
45 | } |
46 | |
47 | bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeData *mimeData, QByteArray *data, |
48 | xcb_atom_t *atomFormat, int *dataFormat) |
49 | { |
50 | if (!data) |
51 | return false; |
52 | |
53 | bool ret = false; |
54 | *atomFormat = a; |
55 | *dataFormat = 8; |
56 | |
57 | if ((a == connection->atom(qatom: QXcbAtom::AtomUTF8_STRING) |
58 | || a == XCB_ATOM_STRING |
59 | || a == connection->atom(qatom: QXcbAtom::AtomTEXT)) |
60 | && QInternalMimeData::hasFormatHelper(mimeType: "text/plain"_L1 , data: mimeData)) { |
61 | if (a == connection->atom(qatom: QXcbAtom::AtomUTF8_STRING)) { |
62 | *data = QInternalMimeData::renderDataHelper(mimeType: "text/plain"_L1 , data: mimeData); |
63 | ret = true; |
64 | } else if (a == XCB_ATOM_STRING || |
65 | a == connection->atom(qatom: QXcbAtom::AtomTEXT)) { |
66 | // ICCCM says STRING is latin1 |
67 | *data = QString::fromUtf8(ba: QInternalMimeData::renderDataHelper( |
68 | mimeType: "text/plain"_L1 , data: mimeData)).toLatin1(); |
69 | ret = true; |
70 | } |
71 | return ret; |
72 | } |
73 | |
74 | QString atomName = mimeAtomToString(connection, a); |
75 | if (QInternalMimeData::hasFormatHelper(mimeType: atomName, data: mimeData)) { |
76 | *data = QInternalMimeData::renderDataHelper(mimeType: atomName, data: mimeData); |
77 | // mimeAtomToString() converts "text/x-moz-url" to "text/uri-list", |
78 | // so QXcbConnection::atomName() has to be used. |
79 | if (atomName == "text/uri-list"_L1 |
80 | && connection->atomName(atom: a) == "text/x-moz-url" ) { |
81 | const QString mozUri = QLatin1StringView(data->split(sep: '\n').constFirst()) + u'\n'; |
82 | data->assign(v: {reinterpret_cast<const char *>(mozUri.data()), mozUri.size() * 2}); |
83 | } else if (atomName == "application/x-color"_L1 ) |
84 | *dataFormat = 16; |
85 | ret = true; |
86 | } else if ((a == XCB_ATOM_PIXMAP || a == XCB_ATOM_BITMAP) && mimeData->hasImage()) { |
87 | ret = true; |
88 | } else if (atomName == "text/plain"_L1 && mimeData->hasFormat(mimetype: "text/uri-list"_L1 )) { |
89 | // Return URLs also as plain text. |
90 | *data = QInternalMimeData::renderDataHelper(mimeType: atomName, data: mimeData); |
91 | ret = true; |
92 | } |
93 | return ret; |
94 | } |
95 | |
96 | QList<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, const QString &format) |
97 | { |
98 | QList<xcb_atom_t> atoms; |
99 | atoms.reserve(asize: 7); |
100 | atoms.append(t: connection->internAtom(name: format.toLatin1())); |
101 | |
102 | // special cases for strings |
103 | if (format == "text/plain"_L1 ) { |
104 | atoms.append(t: connection->atom(qatom: QXcbAtom::AtomUTF8_STRING)); |
105 | atoms.append(t: XCB_ATOM_STRING); |
106 | atoms.append(t: connection->atom(qatom: QXcbAtom::AtomTEXT)); |
107 | } |
108 | |
109 | // special cases for uris |
110 | if (format == "text/uri-list"_L1 ) { |
111 | atoms.append(t: connection->internAtom(name: "text/x-moz-url" )); |
112 | atoms.append(t: connection->internAtom(name: "text/plain" )); |
113 | } |
114 | |
115 | //special cases for images |
116 | if (format == "image/ppm"_L1 ) |
117 | atoms.append(t: XCB_ATOM_PIXMAP); |
118 | if (format == "image/pbm"_L1 ) |
119 | atoms.append(t: XCB_ATOM_BITMAP); |
120 | |
121 | return atoms; |
122 | } |
123 | |
124 | QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a, const QByteArray &d, const QString &format, |
125 | QMetaType requestedType, bool hasUtf8) |
126 | { |
127 | QByteArray data = d; |
128 | QString atomName = mimeAtomToString(connection, a); |
129 | // qDebug() << "mimeConvertDataToFormat" << format << atomName << data; |
130 | |
131 | if (hasUtf8 && atomName == format + ";charset=utf-8"_L1 ) { |
132 | if (requestedType.id() == QMetaType::QString) |
133 | return QString::fromUtf8(ba: data); |
134 | return data; |
135 | } |
136 | |
137 | // special cases for string types |
138 | if (format == "text/plain"_L1 ) { |
139 | if (data.endsWith(c: '\0')) |
140 | data.chop(n: 1); |
141 | if (a == connection->atom(qatom: QXcbAtom::AtomUTF8_STRING)) { |
142 | return QString::fromUtf8(ba: data); |
143 | } |
144 | if (a == XCB_ATOM_STRING || |
145 | a == connection->atom(qatom: QXcbAtom::AtomTEXT)) |
146 | return QString::fromLatin1(ba: data); |
147 | } |
148 | // If data contains UTF16 text, convert it to a string. |
149 | // Firefox uses UTF16 without BOM for text/x-moz-url, "text/html", |
150 | // Google Chrome uses UTF16 without BOM for "text/x-moz-url", |
151 | // UTF16 with BOM for "text/html". |
152 | if ((format == "text/html"_L1 || format == "text/uri-list"_L1 ) |
153 | && data.size() > 1) { |
154 | const quint8 byte0 = data.at(i: 0); |
155 | const quint8 byte1 = data.at(i: 1); |
156 | if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff) |
157 | || (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) { |
158 | const QString str = QString::fromUtf16( |
159 | reinterpret_cast<const char16_t *>(data.constData()), size: data.size() / 2); |
160 | if (!str.isNull()) { |
161 | if (format == "text/uri-list"_L1 ) { |
162 | const auto urls = QStringView{str}.split(sep: u'\n'); |
163 | QList<QVariant> list; |
164 | list.reserve(asize: urls.size()); |
165 | for (const QStringView &s : urls) { |
166 | const QUrl url(s.trimmed().toString()); |
167 | if (url.isValid()) |
168 | list.append(t: url); |
169 | } |
170 | // We expect "text/x-moz-url" as <url><space><title>. |
171 | // The atomName variable is not used because mimeAtomToString() |
172 | // converts "text/x-moz-url" to "text/uri-list". |
173 | if (!list.isEmpty() && connection->atomName(atom: a) == "text/x-moz-url" ) |
174 | return list.constFirst(); |
175 | return list; |
176 | } else { |
177 | return str; |
178 | } |
179 | } |
180 | } |
181 | // 8 byte encoding, remove a possible 0 at the end |
182 | if (data.endsWith(c: '\0')) |
183 | data.chop(n: 1); |
184 | } |
185 | |
186 | if (atomName == format) |
187 | return data; |
188 | |
189 | #if 0 // ### |
190 | // special case for images |
191 | if (format == "image/ppm"_L1 ) { |
192 | if (a == XCB_ATOM_PIXMAP && data.size() == sizeof(Pixmap)) { |
193 | Pixmap xpm = *((Pixmap*)data.data()); |
194 | if (!xpm) |
195 | return QByteArray(); |
196 | Window root; |
197 | int x; |
198 | int y; |
199 | uint width; |
200 | uint height; |
201 | uint border_width; |
202 | uint depth; |
203 | |
204 | XGetGeometry(display, xpm, &root, &x, &y, &width, &height, &border_width, &depth); |
205 | XImage *ximg = XGetImage(display,xpm,x,y,width,height,AllPlanes,depth==1 ? XYPixmap : ZPixmap); |
206 | QImage qimg = QXlibStatic::qimageFromXImage(ximg); |
207 | XDestroyImage(ximg); |
208 | |
209 | QImageWriter imageWriter; |
210 | imageWriter.setFormat("PPMRAW" ); |
211 | QBuffer buf; |
212 | buf.open(QIODevice::WriteOnly); |
213 | imageWriter.setDevice(&buf); |
214 | imageWriter.write(qimg); |
215 | return buf.buffer(); |
216 | } |
217 | } |
218 | #endif |
219 | return QVariant(); |
220 | } |
221 | |
222 | xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString &format, QMetaType requestedType, |
223 | const QList<xcb_atom_t> &atoms, bool *hasUtf8) |
224 | { |
225 | *hasUtf8 = false; |
226 | |
227 | // find matches for string types |
228 | if (format == "text/plain"_L1 ) { |
229 | if (atoms.contains(t: connection->atom(qatom: QXcbAtom::AtomUTF8_STRING))) |
230 | return connection->atom(qatom: QXcbAtom::AtomUTF8_STRING); |
231 | if (atoms.contains(t: XCB_ATOM_STRING)) |
232 | return XCB_ATOM_STRING; |
233 | if (atoms.contains(t: connection->atom(qatom: QXcbAtom::AtomTEXT))) |
234 | return connection->atom(qatom: QXcbAtom::AtomTEXT); |
235 | } |
236 | |
237 | // find matches for uri types |
238 | if (format == "text/uri-list"_L1 ) { |
239 | xcb_atom_t a = connection->internAtom(name: format.toLatin1()); |
240 | if (a && atoms.contains(t: a)) |
241 | return a; |
242 | a = connection->internAtom(name: "text/x-moz-url" ); |
243 | if (a && atoms.contains(t: a)) |
244 | return a; |
245 | } |
246 | |
247 | // find match for image |
248 | if (format == "image/ppm"_L1 ) { |
249 | if (atoms.contains(t: XCB_ATOM_PIXMAP)) |
250 | return XCB_ATOM_PIXMAP; |
251 | } |
252 | |
253 | // for string/text requests try to use a format with a well-defined charset |
254 | // first to avoid encoding problems |
255 | if (requestedType.id() == QMetaType::QString |
256 | && format.startsWith(s: "text/"_L1 ) |
257 | && !format.contains(s: "charset="_L1 )) { |
258 | |
259 | QString formatWithCharset = format; |
260 | formatWithCharset.append(s: ";charset=utf-8"_L1 ); |
261 | |
262 | xcb_atom_t a = connection->internAtom(name: std::move(formatWithCharset).toLatin1()); |
263 | if (a && atoms.contains(t: a)) { |
264 | *hasUtf8 = true; |
265 | return a; |
266 | } |
267 | } |
268 | |
269 | xcb_atom_t a = connection->internAtom(name: format.toLatin1()); |
270 | if (a && atoms.contains(t: a)) |
271 | return a; |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | QT_END_NAMESPACE |
277 | |
278 | #include "moc_qxcbmime.cpp" |
279 | |