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 "qwindowsmime.h"
41 #include "qwindowscontext.h"
42
43 #include <QtGui/private/qinternalmimedata_p.h>
44 #include <QtCore/qbytearraymatcher.h>
45 #include <QtCore/qtextcodec.h>
46 #include <QtCore/qmap.h>
47 #include <QtCore/qurl.h>
48 #include <QtCore/qdir.h>
49 #include <QtCore/qdebug.h>
50 #include <QtCore/qbuffer.h>
51 #include <QtGui/qimagereader.h>
52 #include <QtGui/qimagewriter.h>
53
54 #include <shlobj.h>
55 #include <algorithm>
56
57 QT_BEGIN_NAMESPACE
58
59 /* The MSVC compilers allows multi-byte characters, that has the behavior of
60 * that each character gets shifted into position. 0x73524742 below is for MSVC
61 * equivalent to doing 'sRGB', but this does of course not work
62 * on conformant C++ compilers. */
63 #define BMP_LCS_sRGB 0x73524742
64 #define BMP_LCS_GM_IMAGES 0x00000004L
65
66 struct _CIEXYZ {
67 long ciexyzX, ciexyzY, ciexyzZ;
68 };
69
70 struct _CIEXYZTRIPLE {
71 _CIEXYZ ciexyzRed, ciexyzGreen, ciexyzBlue;
72 };
73
74 struct BMP_BITMAPV5HEADER {
75 DWORD bV5Size;
76 LONG bV5Width;
77 LONG bV5Height;
78 WORD bV5Planes;
79 WORD bV5BitCount;
80 DWORD bV5Compression;
81 DWORD bV5SizeImage;
82 LONG bV5XPelsPerMeter;
83 LONG bV5YPelsPerMeter;
84 DWORD bV5ClrUsed;
85 DWORD bV5ClrImportant;
86 DWORD bV5RedMask;
87 DWORD bV5GreenMask;
88 DWORD bV5BlueMask;
89 DWORD bV5AlphaMask;
90 DWORD bV5CSType;
91 _CIEXYZTRIPLE bV5Endpoints;
92 DWORD bV5GammaRed;
93 DWORD bV5GammaGreen;
94 DWORD bV5GammaBlue;
95 DWORD bV5Intent;
96 DWORD bV5ProfileData;
97 DWORD bV5ProfileSize;
98 DWORD bV5Reserved;
99 };
100 static const int BMP_BITFIELDS = 3;
101
102 static const char dibFormatC[] = "dib";
103
msgConversionError(const char * func,const char * format)104 static inline QByteArray msgConversionError(const char *func, const char *format)
105 {
106 QByteArray msg = func;
107 msg += ": Unable to convert DIB image. The image converter plugin for '";
108 msg += format;
109 msg += "' is not available. Available formats: ";
110 const auto &formats = QImageReader::supportedImageFormats();
111 for (const QByteArray &af : formats) {
112 msg += af;
113 msg += ' ';
114 }
115 return msg;
116 }
117
readDib(QByteArray data)118 static inline QImage readDib(QByteArray data)
119 {
120 QBuffer buffer(&data);
121 buffer.open(QIODevice::ReadOnly);
122 QImageReader reader(&buffer, dibFormatC);
123 if (!reader.canRead()) {
124 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
125 return QImage();
126 }
127 return reader.read();
128 }
129
writeDib(const QImage & img)130 static QByteArray writeDib(const QImage &img)
131 {
132 QByteArray ba;
133 QBuffer buffer(&ba);
134 buffer.open(QIODevice::ReadWrite);
135 QImageWriter writer(&buffer, dibFormatC);
136 if (!writer.canWrite()) {
137 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
138 return ba;
139 }
140 if (!writer.write(img))
141 ba.clear();
142 return ba;
143 }
144
qt_write_dibv5(QDataStream & s,QImage image)145 static bool qt_write_dibv5(QDataStream &s, QImage image)
146 {
147 QIODevice* d = s.device();
148 if (!d->isWritable())
149 return false;
150
151 //depth will be always 32
152 int bpl_bmp = image.width()*4;
153
154 BMP_BITMAPV5HEADER bi;
155 ZeroMemory(&bi, sizeof(bi));
156 bi.bV5Size = sizeof(BMP_BITMAPV5HEADER);
157 bi.bV5Width = image.width();
158 bi.bV5Height = image.height();
159 bi.bV5Planes = 1;
160 bi.bV5BitCount = 32;
161 bi.bV5Compression = BI_BITFIELDS;
162 bi.bV5SizeImage = DWORD(bpl_bmp * image.height());
163 bi.bV5XPelsPerMeter = 0;
164 bi.bV5YPelsPerMeter = 0;
165 bi.bV5ClrUsed = 0;
166 bi.bV5ClrImportant = 0;
167 bi.bV5BlueMask = 0x000000ff;
168 bi.bV5GreenMask = 0x0000ff00;
169 bi.bV5RedMask = 0x00ff0000;
170 bi.bV5AlphaMask = 0xff000000;
171 bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB
172 bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES
173
174 d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
175 if (s.status() != QDataStream::Ok)
176 return false;
177
178 if (image.format() != QImage::Format_ARGB32)
179 image = image.convertToFormat(QImage::Format_ARGB32);
180
181 auto *buf = new uchar[bpl_bmp];
182
183 memset(buf, 0, size_t(bpl_bmp));
184 for (int y=image.height()-1; y>=0; y--) {
185 // write the image bits
186 const QRgb *p = reinterpret_cast<const QRgb *>(image.constScanLine(y));
187 const QRgb *end = p + image.width();
188 uchar *b = buf;
189 while (p < end) {
190 int alpha = qAlpha(*p);
191 if (alpha) {
192 *b++ = uchar(qBlue(*p));
193 *b++ = uchar(qGreen(*p));
194 *b++ = uchar(qRed(*p));
195 } else {
196 //white for fully transparent pixels.
197 *b++ = 0xff;
198 *b++ = 0xff;
199 *b++ = 0xff;
200 }
201 *b++ = uchar(alpha);
202 p++;
203 }
204 d->write(reinterpret_cast<const char *>(buf), bpl_bmp);
205 if (s.status() != QDataStream::Ok) {
206 delete[] buf;
207 return false;
208 }
209 }
210 delete[] buf;
211 return true;
212 }
213
calc_shift(int mask)214 static int calc_shift(int mask)
215 {
216 int result = 0;
217 while (!(mask & 1)) {
218 result++;
219 mask >>= 1;
220 }
221 return result;
222 }
223
224 //Supports only 32 bit DIBV5
qt_read_dibv5(QDataStream & s,QImage & image)225 static bool qt_read_dibv5(QDataStream &s, QImage &image)
226 {
227 BMP_BITMAPV5HEADER bi;
228 QIODevice* d = s.device();
229 if (d->atEnd())
230 return false;
231
232 d->read(reinterpret_cast<char *>(&bi), sizeof(bi)); // read BITMAPV5HEADER header
233 if (s.status() != QDataStream::Ok)
234 return false;
235
236 const int nbits = bi.bV5BitCount;
237 if (nbits != 32 || bi.bV5Planes != 1 || bi.bV5Compression != BMP_BITFIELDS)
238 return false; //Unsupported DIBV5 format
239
240 const int w = bi.bV5Width;
241 int h = bi.bV5Height;
242 const int red_mask = int(bi.bV5RedMask);
243 const int green_mask = int(bi.bV5GreenMask);
244 const int blue_mask = int(bi.bV5BlueMask);
245 const int alpha_mask = int(bi.bV5AlphaMask);
246
247 const QImage::Format format = QImage::Format_ARGB32;
248
249 if (bi.bV5Height < 0)
250 h = -h; // support images with negative height
251 if (image.size() != QSize(w, h) || image.format() != format) {
252 image = QImage(w, h, format);
253 if (image.isNull()) // could not create image
254 return false;
255 }
256 image.setDotsPerMeterX(bi.bV5XPelsPerMeter);
257 image.setDotsPerMeterY(bi.bV5YPelsPerMeter);
258
259 const int red_shift = calc_shift(red_mask);
260 const int green_shift = calc_shift(green_mask);
261 const int blue_shift = calc_shift(blue_mask);
262 const int alpha_shift = alpha_mask ? calc_shift(alpha_mask) : 0u;
263
264 const int bpl = image.bytesPerLine();
265 uchar *data = image.bits();
266
267 auto *buf24 = new uchar[bpl];
268 const int bpl24 = ((w * nbits + 31) / 32) * 4;
269
270 while (--h >= 0) {
271 QRgb *p = reinterpret_cast<QRgb *>(data + h * bpl);
272 QRgb *end = p + w;
273 if (d->read(reinterpret_cast<char *>(buf24), bpl24) != bpl24)
274 break;
275 const uchar *b = buf24;
276 while (p < end) {
277 const int c = *b | (*(b + 1)) << 8 | (*(b + 2)) << 16 | (*(b + 3)) << 24;
278 *p++ = qRgba(((c & red_mask) >> red_shift) ,
279 ((c & green_mask) >> green_shift),
280 ((c & blue_mask) >> blue_shift),
281 ((c & alpha_mask) >> alpha_shift));
282 b += 4;
283 }
284 }
285 delete[] buf24;
286
287 if (bi.bV5Height < 0) {
288 // Flip the image
289 auto *buf = new uchar[bpl];
290 h = -bi.bV5Height;
291 for (int y = 0; y < h/2; ++y) {
292 memcpy(buf, data + y * bpl, size_t(bpl));
293 memcpy(data + y*bpl, data + (h - y -1) * bpl, size_t(bpl));
294 memcpy(data + (h - y -1 ) * bpl, buf, size_t(bpl));
295 }
296 delete [] buf;
297 }
298
299 return true;
300 }
301
302 // helpers for using global memory
303
getCf(const FORMATETC & formatetc)304 static int getCf(const FORMATETC &formatetc)
305 {
306 return formatetc.cfFormat;
307 }
308
setCf(int cf)309 static FORMATETC setCf(int cf)
310 {
311 FORMATETC formatetc;
312 formatetc.cfFormat = CLIPFORMAT(cf);
313 formatetc.dwAspect = DVASPECT_CONTENT;
314 formatetc.lindex = -1;
315 formatetc.ptd = nullptr;
316 formatetc.tymed = TYMED_HGLOBAL;
317 return formatetc;
318 }
319
setData(const QByteArray & data,STGMEDIUM * pmedium)320 static bool setData(const QByteArray &data, STGMEDIUM *pmedium)
321 {
322 HGLOBAL hData = GlobalAlloc(0, SIZE_T(data.size()));
323 if (!hData)
324 return false;
325
326 void *out = GlobalLock(hData);
327 memcpy(out, data.data(), size_t(data.size()));
328 GlobalUnlock(hData);
329 pmedium->tymed = TYMED_HGLOBAL;
330 pmedium->hGlobal = hData;
331 pmedium->pUnkForRelease = nullptr;
332 return true;
333 }
334
getData(int cf,IDataObject * pDataObj,int lindex=-1)335 static QByteArray getData(int cf, IDataObject *pDataObj, int lindex = -1)
336 {
337 QByteArray data;
338 FORMATETC formatetc = setCf(cf);
339 formatetc.lindex = lindex;
340 STGMEDIUM s;
341 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
342 const void *val = GlobalLock(s.hGlobal);
343 data = QByteArray::fromRawData(reinterpret_cast<const char *>(val), int(GlobalSize(s.hGlobal)));
344 data.detach();
345 GlobalUnlock(s.hGlobal);
346 ReleaseStgMedium(&s);
347 } else {
348 //Try reading IStream data
349 formatetc.tymed = TYMED_ISTREAM;
350 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
351 char szBuffer[4096];
352 ULONG actualRead = 0;
353 LARGE_INTEGER pos = {{0, 0}};
354 //Move to front (can fail depending on the data model implemented)
355 HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
356 while(SUCCEEDED(hr)){
357 hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
358 if (SUCCEEDED(hr) && actualRead > 0) {
359 data += QByteArray::fromRawData(szBuffer, int(actualRead));
360 }
361 if (actualRead != sizeof(szBuffer))
362 break;
363 }
364 data.detach();
365 ReleaseStgMedium(&s);
366 }
367 }
368 return data;
369 }
370
canGetData(int cf,IDataObject * pDataObj)371 static bool canGetData(int cf, IDataObject * pDataObj)
372 {
373 FORMATETC formatetc = setCf(cf);
374 if (pDataObj->QueryGetData(&formatetc) != S_OK){
375 formatetc.tymed = TYMED_ISTREAM;
376 return pDataObj->QueryGetData(&formatetc) == S_OK;
377 }
378 return true;
379 }
380
381 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug d,const FORMATETC & tc)382 QDebug operator<<(QDebug d, const FORMATETC &tc)
383 {
384 QDebugStateSaver saver(d);
385 d.nospace();
386 d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
387 switch (tc.cfFormat) {
388 case CF_TEXT:
389 d << "CF_TEXT";
390 break;
391 case CF_BITMAP:
392 d << "CF_BITMAP";
393 break;
394 case CF_TIFF:
395 d << "CF_TIFF";
396 break;
397 case CF_OEMTEXT:
398 d << "CF_OEMTEXT";
399 break;
400 case CF_DIB:
401 d << "CF_DIB";
402 break;
403 case CF_DIBV5:
404 d << "CF_DIBV5";
405 break;
406 case CF_UNICODETEXT:
407 d << "CF_UNICODETEXT";
408 break;
409 case CF_ENHMETAFILE:
410 d << "CF_ENHMETAFILE";
411 break;
412 default:
413 d << QWindowsMimeConverter::clipboardFormatName(tc.cfFormat);
414 break;
415 }
416 d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
417 << ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
418 return d;
419 }
420
operator <<(QDebug d,IDataObject * dataObj)421 QDebug operator<<(QDebug d, IDataObject *dataObj)
422 {
423 QDebugStateSaver saver(d);
424 d.nospace();
425 d.noquote();
426 d << "IDataObject(";
427 if (dataObj) { // Output formats contained in IDataObject.
428 IEnumFORMATETC *enumFormatEtc;
429 if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
430 FORMATETC formatEtc[1];
431 ULONG fetched;
432 if (SUCCEEDED(enumFormatEtc->Reset())) {
433 while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
434 d << formatEtc[0] << ',';
435 enumFormatEtc->Release();
436 }
437 }
438 } else {
439 d << '0';
440 }
441 d << ')';
442 return d;
443 }
444 #endif // !QT_NO_DEBUG_STREAM
445
446 /*!
447 \class QWindowsMime
448 \brief The QWindowsMime class maps open-standard MIME to Window Clipboard formats.
449 \internal
450
451 Qt's drag-and-drop and clipboard facilities use the MIME standard.
452 On X11, this maps trivially to the Xdnd protocol, but on Windows
453 although some applications use MIME types to describe clipboard
454 formats, others use arbitrary non-standardized naming conventions,
455 or unnamed built-in formats of Windows.
456
457 By instantiating subclasses of QWindowsMime that provide conversions
458 between Windows Clipboard and MIME formats, you can convert
459 proprietary clipboard formats to MIME formats.
460
461 Qt has predefined support for the following Windows Clipboard formats:
462
463 \table
464 \header \li Windows Format \li Equivalent MIME type
465 \row \li \c CF_UNICODETEXT \li \c text/plain
466 \row \li \c CF_TEXT \li \c text/plain
467 \row \li \c CF_DIB \li \c{image/xyz}, where \c xyz is
468 a \l{QImageWriter::supportedImageFormats()}{Qt image format}
469 \row \li \c CF_HDROP \li \c text/uri-list
470 \row \li \c CF_INETURL \li \c text/uri-list
471 \row \li \c CF_HTML \li \c text/html
472 \endtable
473
474 An example use of this class would be to map the Windows Metafile
475 clipboard format (\c CF_METAFILEPICT) to and from the MIME type
476 \c{image/x-wmf}. This conversion might simply be adding or removing
477 a header, or even just passing on the data. See \l{Drag and Drop}
478 for more information on choosing and definition MIME types.
479
480 You can check if a MIME type is convertible using canConvertFromMime() and
481 can perform conversions with convertToMime() and convertFromMime().
482
483 \sa QWindowsMimeConverter
484 */
485
486 /*!
487 Constructs a new conversion object, adding it to the globally accessed
488 list of available converters.
489 */
490 QWindowsMime::QWindowsMime() = default;
491
492 /*!
493 Destroys a conversion object, removing it from the global
494 list of available converters.
495 */
496 QWindowsMime::~QWindowsMime() = default;
497
498 /*!
499 Registers the MIME type \a mime, and returns an ID number
500 identifying the format on Windows.
501 */
registerMimeType(const QString & mime)502 int QWindowsMime::registerMimeType(const QString &mime)
503 {
504 const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mime.utf16()));
505 if (!f)
506 qErrnoWarning("QWindowsMime::registerMimeType: Failed to register clipboard format");
507
508 return int(f);
509 }
510
511 /*!
512 \fn bool QWindowsMime::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
513
514 Returns \c true if the converter can convert from the \a mimeData to
515 the format specified in \a formatetc.
516
517 All subclasses must reimplement this pure virtual function.
518 */
519
520 /*!
521 \fn bool QWindowsMime::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
522
523 Returns \c true if the converter can convert to the \a mimeType from
524 the available formats in \a pDataObj.
525
526 All subclasses must reimplement this pure virtual function.
527 */
528
529 /*!
530 \fn QString QWindowsMime::mimeForFormat(const FORMATETC &formatetc) const
531
532 Returns the mime type that will be created form the format specified
533 in \a formatetc, or an empty string if this converter does not support
534 \a formatetc.
535
536 All subclasses must reimplement this pure virtual function.
537 */
538
539 /*!
540 \fn QVector<FORMATETC> QWindowsMime::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
541
542 Returns a QVector of FORMATETC structures representing the different windows clipboard
543 formats that can be provided for the \a mimeType from the \a mimeData.
544
545 All subclasses must reimplement this pure virtual function.
546 */
547
548 /*!
549 \fn QVariant QWindowsMime::convertToMime(const QString &mimeType, IDataObject *pDataObj,
550 QVariant::Type preferredType) const
551
552 Returns a QVariant containing the converted data for \a mimeType from \a pDataObj.
553 If possible the QVariant should be of the \a preferredType to avoid needless conversions.
554
555 All subclasses must reimplement this pure virtual function.
556 */
557
558 /*!
559 \fn bool QWindowsMime::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
560
561 Convert the \a mimeData to the format specified in \a formatetc.
562 The converted data should then be placed in \a pmedium structure.
563
564 Return true if the conversion was successful.
565
566 All subclasses must reimplement this pure virtual function.
567 */
568
569 class QWindowsMimeText : public QWindowsMime
570 {
571 public:
572 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
573 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const override;
574 QString mimeForFormat(const FORMATETC &formatetc) const override;
575 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
576 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
577 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
578 };
579
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const580 bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
581 {
582 int cf = getCf(formatetc);
583 return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText();
584 }
585
586 /*
587 text/plain is defined as using CRLF, but so many programs don't,
588 and programmers just look for '\n' in strings.
589 Windows really needs CRLF, so we ensure it here.
590 */
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const591 bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
592 {
593 if (canConvertFromMime(formatetc, mimeData)) {
594 QByteArray data;
595 int cf = getCf(formatetc);
596 if (cf == CF_TEXT) {
597 data = mimeData->text().toLocal8Bit();
598 // Anticipate required space for CRLFs at 1/40
599 int maxsize=data.size()+data.size()/40+3;
600 QByteArray r(maxsize, '\0');
601 char* o = r.data();
602 const char* d = data.data();
603 const int s = data.size();
604 bool cr=false;
605 int j=0;
606 for (int i=0; i<s; i++) {
607 char c = d[i];
608 if (c=='\r')
609 cr=true;
610 else {
611 if (c=='\n') {
612 if (!cr)
613 o[j++]='\r';
614 }
615 cr=false;
616 }
617 o[j++]=c;
618 if (j+3 >= maxsize) {
619 maxsize += maxsize/4;
620 r.resize(maxsize);
621 o = r.data();
622 }
623 }
624 o[j]=0;
625 return setData(r, pmedium);
626 }
627 if (cf == CF_UNICODETEXT) {
628 QString str = mimeData->text();
629 const QChar *u = str.unicode();
630 QString res;
631 const int s = str.length();
632 int maxsize = s + s/40 + 3;
633 res.resize(maxsize);
634 int ri = 0;
635 bool cr = false;
636 for (int i=0; i < s; ++i) {
637 if (*u == u'\r')
638 cr = true;
639 else {
640 if (*u == u'\n' && !cr)
641 res[ri++] = u'\r';
642 cr = false;
643 }
644 res[ri++] = *u;
645 if (ri+3 >= maxsize) {
646 maxsize += maxsize/4;
647 res.resize(maxsize);
648 }
649 ++u;
650 }
651 res.truncate(ri);
652 const int byteLength = res.length() * int(sizeof(ushort));
653 QByteArray r(byteLength + 2, '\0');
654 memcpy(r.data(), res.unicode(), size_t(byteLength));
655 r[byteLength] = 0;
656 r[byteLength+1] = 0;
657 return setData(r, pmedium);
658 }
659 }
660 return false;
661 }
662
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const663 bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
664 {
665 return mimeType.startsWith(u"text/plain")
666 && (canGetData(CF_UNICODETEXT, pDataObj)
667 || canGetData(CF_TEXT, pDataObj));
668 }
669
mimeForFormat(const FORMATETC & formatetc) const670 QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
671 {
672 int cf = getCf(formatetc);
673 if (cf == CF_UNICODETEXT || cf == CF_TEXT)
674 return QStringLiteral("text/plain");
675 return QString();
676 }
677
678
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const679 QVector<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
680 {
681 QVector<FORMATETC> formatics;
682 if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
683 formatics += setCf(CF_UNICODETEXT);
684 formatics += setCf(CF_TEXT);
685 }
686 return formatics;
687 }
688
convertToMime(const QString & mime,LPDATAOBJECT pDataObj,QVariant::Type preferredType) const689 QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
690 {
691 QVariant ret;
692
693 if (canConvertToMime(mime, pDataObj)) {
694 QString str;
695 QByteArray data = getData(CF_UNICODETEXT, pDataObj);
696 if (!data.isEmpty()) {
697 str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
698 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
699 } else {
700 data = getData(CF_TEXT, pDataObj);
701 if (!data.isEmpty()) {
702 const char* d = data.data();
703 const unsigned s = qstrlen(d);
704 QByteArray r(data.size()+1, '\0');
705 char* o = r.data();
706 int j=0;
707 for (unsigned i = 0; i < s; ++i) {
708 char c = d[i];
709 if (c!='\r')
710 o[j++]=c;
711 }
712 o[j]=0;
713 str = QString::fromLocal8Bit(r);
714 }
715 }
716 if (preferredType == QVariant::String)
717 ret = str;
718 else
719 ret = std::move(str).toUtf8();
720 }
721 qCDebug(lcQpaMime) << __FUNCTION__ << ret;
722 return ret;
723 }
724
725 class QWindowsMimeURI : public QWindowsMime
726 {
727 public:
728 QWindowsMimeURI();
729 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
730 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const override;
731 QString mimeForFormat(const FORMATETC &formatetc) const override;
732 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
733 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
734 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
735 private:
736 int CF_INETURL_W; // wide char version
737 int CF_INETURL;
738 };
739
QWindowsMimeURI()740 QWindowsMimeURI::QWindowsMimeURI()
741 {
742 CF_INETURL_W = QWindowsMime::registerMimeType(QStringLiteral("UniformResourceLocatorW"));
743 CF_INETURL = QWindowsMime::registerMimeType(QStringLiteral("UniformResourceLocator"));
744 }
745
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const746 bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
747 {
748 if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
749 const auto urls = mimeData->urls();
750 for (const QUrl &url : urls) {
751 if (url.isLocalFile())
752 return true;
753 }
754 }
755 return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
756 }
757
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const758 bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
759 {
760 if (canConvertFromMime(formatetc, mimeData)) {
761 if (getCf(formatetc) == CF_HDROP) {
762 const auto &urls = mimeData->urls();
763 QStringList fileNames;
764 int size = sizeof(DROPFILES)+2;
765 for (const QUrl &url : urls) {
766 const QString fn = QDir::toNativeSeparators(url.toLocalFile());
767 if (!fn.isEmpty()) {
768 size += sizeof(ushort) * size_t(fn.length() + 1);
769 fileNames.append(fn);
770 }
771 }
772
773 QByteArray result(size, '\0');
774 auto* d = reinterpret_cast<DROPFILES *>(result.data());
775 d->pFiles = sizeof(DROPFILES);
776 GetCursorPos(&d->pt); // try
777 d->fNC = true;
778 char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
779
780 d->fWide = true;
781 auto *f = reinterpret_cast<wchar_t *>(files);
782 for (int i=0; i<fileNames.size(); i++) {
783 const auto l = size_t(fileNames.at(i).length());
784 memcpy(f, fileNames.at(i).utf16(), l * sizeof(ushort));
785 f += l;
786 *f++ = 0;
787 }
788 *f = 0;
789
790 return setData(result, pmedium);
791 }
792 if (getCf(formatetc) == CF_INETURL_W) {
793 const auto urls = mimeData->urls();
794 QByteArray result;
795 if (!urls.isEmpty()) {
796 QString url = urls.at(0).toString();
797 result = QByteArray(reinterpret_cast<const char *>(url.utf16()),
798 url.length() * int(sizeof(ushort)));
799 }
800 result.append('\0');
801 result.append('\0');
802 return setData(result, pmedium);
803 }
804 if (getCf(formatetc) == CF_INETURL) {
805 const auto urls = mimeData->urls();
806 QByteArray result;
807 if (!urls.isEmpty())
808 result = urls.at(0).toString().toLocal8Bit();
809 return setData(result, pmedium);
810 }
811 }
812
813 return false;
814 }
815
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const816 bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
817 {
818 return mimeType == u"text/uri-list"
819 && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
820 }
821
mimeForFormat(const FORMATETC & formatetc) const822 QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
823 {
824 QString format;
825 if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
826 format = QStringLiteral("text/uri-list");
827 return format;
828 }
829
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const830 QVector<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
831 {
832 QVector<FORMATETC> formatics;
833 if (mimeType == u"text/uri-list") {
834 if (canConvertFromMime(setCf(CF_HDROP), mimeData))
835 formatics += setCf(CF_HDROP);
836 if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
837 formatics += setCf(CF_INETURL_W);
838 if (canConvertFromMime(setCf(CF_INETURL), mimeData))
839 formatics += setCf(CF_INETURL);
840 }
841 return formatics;
842 }
843
convertToMime(const QString & mimeType,LPDATAOBJECT pDataObj,QVariant::Type preferredType) const844 QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
845 {
846 if (mimeType == u"text/uri-list") {
847 if (canGetData(CF_HDROP, pDataObj)) {
848 QList<QVariant> urls;
849
850 QByteArray data = getData(CF_HDROP, pDataObj);
851 if (data.isEmpty())
852 return QVariant();
853
854 const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.constData());
855 if (hdrop->fWide) {
856 const auto *filesw = reinterpret_cast<const wchar_t *>(data.constData() + hdrop->pFiles);
857 int i = 0;
858 while (filesw[i]) {
859 QString fileurl = QString::fromWCharArray(filesw + i);
860 urls += QUrl::fromLocalFile(fileurl);
861 i += fileurl.length()+1;
862 }
863 } else {
864 const char* files = reinterpret_cast<const char *>(data.constData() + hdrop->pFiles);
865 int i=0;
866 while (files[i]) {
867 urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
868 i += int(strlen(files+i))+1;
869 }
870 }
871
872 if (preferredType == QVariant::Url && urls.size() == 1)
873 return urls.at(0);
874 if (!urls.isEmpty())
875 return urls;
876 } else if (canGetData(CF_INETURL_W, pDataObj)) {
877 QByteArray data = getData(CF_INETURL_W, pDataObj);
878 if (data.isEmpty())
879 return QVariant();
880 return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
881 } else if (canGetData(CF_INETURL, pDataObj)) {
882 QByteArray data = getData(CF_INETURL, pDataObj);
883 if (data.isEmpty())
884 return QVariant();
885 return QUrl(QString::fromLocal8Bit(data.constData()));
886 }
887 }
888 return QVariant();
889 }
890
891 class QWindowsMimeHtml : public QWindowsMime
892 {
893 public:
894 QWindowsMimeHtml();
895
896 // for converting from Qt
897 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
898 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
899 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
900
901 // for converting to Qt
902 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
903 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
904 QString mimeForFormat(const FORMATETC &formatetc) const override;
905
906 private:
907 int CF_HTML;
908 };
909
QWindowsMimeHtml()910 QWindowsMimeHtml::QWindowsMimeHtml()
911 {
912 CF_HTML = QWindowsMime::registerMimeType(QStringLiteral("HTML Format"));
913 }
914
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const915 QVector<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
916 {
917 QVector<FORMATETC> formatetcs;
918 if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
919 formatetcs += setCf(CF_HTML);
920 return formatetcs;
921 }
922
mimeForFormat(const FORMATETC & formatetc) const923 QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
924 {
925 if (getCf(formatetc) == CF_HTML)
926 return QStringLiteral("text/html");
927 return QString();
928 }
929
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const930 bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
931 {
932 return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
933 }
934
935
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const936 bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
937 {
938 return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
939 }
940
941 /*
942 The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
943 in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
944
945 Version: 1.0
946 StartHTML:xxxxxxxxxx
947 EndHTML:xxxxxxxxxx
948 StartFragment:xxxxxxxxxx
949 EndFragment:xxxxxxxxxx
950 ...html...
951
952 */
convertToMime(const QString & mime,IDataObject * pDataObj,QVariant::Type preferredType) const953 QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const
954 {
955 Q_UNUSED(preferredType);
956 QVariant result;
957 if (canConvertToMime(mime, pDataObj)) {
958 QByteArray html = getData(CF_HTML, pDataObj);
959 static Q_RELAXED_CONSTEXPR auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
960 static Q_RELAXED_CONSTEXPR auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:");
961 qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
962 int start = startMatcher.indexIn(html);
963 int end = endMatcher.indexIn(html);
964
965 if (start != -1) {
966 int startOffset = start + 10;
967 int i = startOffset;
968 while (html.at(i) != '\r' && html.at(i) != '\n')
969 ++i;
970 QByteArray bytecount = html.mid(startOffset, i - startOffset);
971 start = bytecount.toInt();
972 }
973
974 if (end != -1) {
975 int endOffset = end + 8;
976 int i = endOffset ;
977 while (html.at(i) != '\r' && html.at(i) != '\n')
978 ++i;
979 QByteArray bytecount = html.mid(endOffset , i - endOffset);
980 end = bytecount.toInt();
981 }
982
983 if (end > start && start > 0) {
984 html = html.mid(start, end - start);
985 html.replace('\r', "");
986 result = QString::fromUtf8(html);
987 }
988 }
989 return result;
990 }
991
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const992 bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
993 {
994 if (canConvertFromMime(formatetc, mimeData)) {
995 QByteArray data = mimeData->html().toUtf8();
996 QByteArray result =
997 "Version:1.0\r\n" // 0-12
998 "StartHTML:0000000107\r\n" // 13-35
999 "EndHTML:0000000000\r\n" // 36-55
1000 "StartFragment:0000000000\r\n" // 56-81
1001 "EndFragment:0000000000\r\n\r\n"; // 82-107
1002
1003 static Q_RELAXED_CONSTEXPR auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
1004 static Q_RELAXED_CONSTEXPR auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
1005
1006 if (startFragmentMatcher.indexIn(data) == -1)
1007 result += "<!--StartFragment-->";
1008 result += data;
1009 if (endFragmentMatcher.indexIn(data) == -1)
1010 result += "<!--EndFragment-->";
1011
1012 // set the correct number for EndHTML
1013 QByteArray pos = QByteArray::number(result.size());
1014 memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
1015
1016 // set correct numbers for StartFragment and EndFragment
1017 pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
1018 memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
1019 pos = QByteArray::number(endFragmentMatcher.indexIn(result));
1020 memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
1021
1022 return setData(result, pmedium);
1023 }
1024 return false;
1025 }
1026
1027
1028 #ifndef QT_NO_IMAGEFORMAT_BMP
1029 class QWindowsMimeImage : public QWindowsMime
1030 {
1031 public:
1032 QWindowsMimeImage();
1033 // for converting from Qt
1034 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1035 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1036 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1037
1038 // for converting to Qt
1039 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1040 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1041 QString mimeForFormat(const FORMATETC &formatetc) const override;
1042 private:
1043 bool hasOriginalDIBV5(IDataObject *pDataObj) const;
1044 UINT CF_PNG;
1045 };
1046
QWindowsMimeImage()1047 QWindowsMimeImage::QWindowsMimeImage()
1048 {
1049 CF_PNG = RegisterClipboardFormat(L"PNG");
1050 }
1051
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const1052 QVector<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1053 {
1054 QVector<FORMATETC> formatetcs;
1055 if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
1056 //add DIBV5 if image has alpha channel. Do not add CF_PNG here as it will confuse MS Office (QTBUG47656).
1057 auto image = qvariant_cast<QImage>(mimeData->imageData());
1058 if (!image.isNull() && image.hasAlphaChannel())
1059 formatetcs += setCf(CF_DIBV5);
1060 formatetcs += setCf(CF_DIB);
1061 }
1062 if (!formatetcs.isEmpty())
1063 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1064 return formatetcs;
1065 }
1066
mimeForFormat(const FORMATETC & formatetc) const1067 QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
1068 {
1069 int cf = getCf(formatetc);
1070 if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
1071 return QStringLiteral("application/x-qt-image");
1072 return QString();
1073 }
1074
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1075 bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1076 {
1077 return mimeType == u"application/x-qt-image"
1078 && (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
1079 }
1080
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1081 bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1082 {
1083 int cf = getCf(formatetc);
1084 if (!mimeData->hasImage())
1085 return false;
1086 const QImage image = qvariant_cast<QImage>(mimeData->imageData());
1087 if (image.isNull())
1088 return false;
1089 // QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
1090 // cannot handle it.
1091 return cf == CF_DIBV5 || cf == CF_DIB
1092 || (cf == int(CF_PNG) && image.hasAlphaChannel());
1093 }
1094
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1095 bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1096 {
1097 int cf = getCf(formatetc);
1098 if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
1099 auto img = qvariant_cast<QImage>(mimeData->imageData());
1100 if (img.isNull())
1101 return false;
1102 QByteArray ba;
1103 if (cf == CF_DIB) {
1104 if (img.format() > QImage::Format_ARGB32)
1105 img = img.convertToFormat(QImage::Format_RGB32);
1106 const QByteArray ba = writeDib(img);
1107 if (!ba.isEmpty())
1108 return setData(ba, pmedium);
1109 } else if (cf == int(CF_PNG)) {
1110 QBuffer buffer(&ba);
1111 const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
1112 buffer.close();
1113 if (written)
1114 return setData(ba, pmedium);
1115 } else {
1116 QDataStream s(&ba, QIODevice::WriteOnly);
1117 s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
1118 if (qt_write_dibv5(s, img))
1119 return setData(ba, pmedium);
1120 }
1121 }
1122 return false;
1123 }
1124
hasOriginalDIBV5(IDataObject * pDataObj) const1125 bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
1126 {
1127 bool isSynthesized = true;
1128 IEnumFORMATETC *pEnum = nullptr;
1129 HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
1130 if (res == S_OK && pEnum) {
1131 FORMATETC fc;
1132 while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
1133 if (fc.ptd)
1134 CoTaskMemFree(fc.ptd);
1135 if (fc.cfFormat == CF_DIB)
1136 break;
1137 if (fc.cfFormat == CF_DIBV5) {
1138 isSynthesized = false;
1139 break;
1140 }
1141 }
1142 pEnum->Release();
1143 }
1144 return !isSynthesized;
1145 }
1146
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1147 QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1148 {
1149 Q_UNUSED(preferredType);
1150 QVariant result;
1151 if (mimeType != u"application/x-qt-image")
1152 return result;
1153 //Try to convert from a format which has more data
1154 //DIBV5, use only if its is not synthesized
1155 if (canGetData(CF_DIBV5, pDataObj) && hasOriginalDIBV5(pDataObj)) {
1156 QImage img;
1157 QByteArray data = getData(CF_DIBV5, pDataObj);
1158 QDataStream s(&data, QIODevice::ReadOnly);
1159 s.setByteOrder(QDataStream::LittleEndian);
1160 if (qt_read_dibv5(s, img)) { // #### supports only 32bit DIBV5
1161 return img;
1162 }
1163 }
1164 //PNG, MS Office place this (undocumented)
1165 if (canGetData(CF_PNG, pDataObj)) {
1166 QImage img;
1167 QByteArray data = getData(CF_PNG, pDataObj);
1168 if (img.loadFromData(data, "PNG")) {
1169 return img;
1170 }
1171 }
1172 //Fallback to DIB
1173 if (canGetData(CF_DIB, pDataObj)) {
1174 const QImage img = readDib(getData(CF_DIB, pDataObj));
1175 if (!img.isNull())
1176 return img;
1177 }
1178 // Failed
1179 return result;
1180 }
1181 #endif
1182
1183 class QBuiltInMimes : public QWindowsMime
1184 {
1185 public:
1186 QBuiltInMimes();
1187
1188 // for converting from Qt
1189 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1190 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1191 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1192
1193 // for converting to Qt
1194 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1195 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1196 QString mimeForFormat(const FORMATETC &formatetc) const override;
1197
1198 private:
1199 QMap<int, QString> outFormats;
1200 QMap<int, QString> inFormats;
1201 };
1202
QBuiltInMimes()1203 QBuiltInMimes::QBuiltInMimes()
1204 : QWindowsMime()
1205 {
1206 outFormats.insert(QWindowsMime::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
1207 inFormats.insert(QWindowsMime::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
1208 }
1209
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1210 bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1211 {
1212 // really check
1213 return formatetc.tymed & TYMED_HGLOBAL
1214 && outFormats.contains(formatetc.cfFormat)
1215 && mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
1216 }
1217
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1218 bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1219 {
1220 if (canConvertFromMime(formatetc, mimeData)) {
1221 QByteArray data;
1222 if (outFormats.value(getCf(formatetc)) == u"text/html") {
1223 // text/html is in wide chars on windows (compatible with mozillia)
1224 QString html = mimeData->html();
1225 // same code as in the text converter up above
1226 const QChar *u = html.unicode();
1227 QString res;
1228 const int s = html.length();
1229 int maxsize = s + s/40 + 3;
1230 res.resize(maxsize);
1231 int ri = 0;
1232 bool cr = false;
1233 for (int i=0; i < s; ++i) {
1234 if (*u == u'\r')
1235 cr = true;
1236 else {
1237 if (*u == u'\n' && !cr)
1238 res[ri++] = u'\r';
1239 cr = false;
1240 }
1241 res[ri++] = *u;
1242 if (ri+3 >= maxsize) {
1243 maxsize += maxsize/4;
1244 res.resize(maxsize);
1245 }
1246 ++u;
1247 }
1248 res.truncate(ri);
1249 const int byteLength = res.length() * int(sizeof(ushort));
1250 QByteArray r(byteLength + 2, '\0');
1251 memcpy(r.data(), res.unicode(), size_t(byteLength));
1252 r[byteLength] = 0;
1253 r[byteLength+1] = 0;
1254 data = r;
1255 } else {
1256 #if QT_CONFIG(draganddrop)
1257 data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
1258 #endif // QT_CONFIG(draganddrop)
1259 }
1260 return setData(data, pmedium);
1261 }
1262 return false;
1263 }
1264
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const1265 QVector<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1266 {
1267 QVector<FORMATETC> formatetcs;
1268 const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
1269 if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
1270 formatetcs += setCf(mit.key());
1271 return formatetcs;
1272 }
1273
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1274 bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1275 {
1276 const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
1277 return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
1278 }
1279
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1280 QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1281 {
1282 QVariant val;
1283 if (canConvertToMime(mimeType, pDataObj)) {
1284 QByteArray data = getData(inFormats.key(mimeType), pDataObj);
1285 if (!data.isEmpty()) {
1286 qCDebug(lcQpaMime) << __FUNCTION__;
1287 if (mimeType == u"text/html" && preferredType == QVariant::String) {
1288 // text/html is in wide chars on windows (compatible with Mozilla)
1289 val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
1290 } else {
1291 val = data; // it should be enough to return the data and let QMimeData do the rest.
1292 }
1293 }
1294 }
1295 return val;
1296 }
1297
mimeForFormat(const FORMATETC & formatetc) const1298 QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
1299 {
1300 return inFormats.value(getCf(formatetc));
1301 }
1302
1303
1304 class QLastResortMimes : public QWindowsMime
1305 {
1306 public:
1307
1308 QLastResortMimes();
1309 // for converting from Qt
1310 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1311 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1312 QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1313
1314 // for converting to Qt
1315 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1316 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1317 QString mimeForFormat(const FORMATETC &formatetc) const override;
1318
1319 private:
1320 mutable QMap<int, QString> formats;
1321 static QStringList ianaTypes;
1322 static QStringList excludeList;
1323 };
1324
1325 QStringList QLastResortMimes::ianaTypes;
1326 QStringList QLastResortMimes::excludeList;
1327
QLastResortMimes()1328 QLastResortMimes::QLastResortMimes()
1329 {
1330 //MIME Media-Types
1331 if (ianaTypes.isEmpty()) {
1332 ianaTypes.append(QStringLiteral("application/"));
1333 ianaTypes.append(QStringLiteral("audio/"));
1334 ianaTypes.append(QStringLiteral("example/"));
1335 ianaTypes.append(QStringLiteral("image/"));
1336 ianaTypes.append(QStringLiteral("message/"));
1337 ianaTypes.append(QStringLiteral("model/"));
1338 ianaTypes.append(QStringLiteral("multipart/"));
1339 ianaTypes.append(QStringLiteral("text/"));
1340 ianaTypes.append(QStringLiteral("video/"));
1341 }
1342 //Types handled by other classes
1343 if (excludeList.isEmpty()) {
1344 excludeList.append(QStringLiteral("HTML Format"));
1345 excludeList.append(QStringLiteral("UniformResourceLocator"));
1346 excludeList.append(QStringLiteral("text/html"));
1347 excludeList.append(QStringLiteral("text/plain"));
1348 excludeList.append(QStringLiteral("text/uri-list"));
1349 excludeList.append(QStringLiteral("application/x-qt-image"));
1350 excludeList.append(QStringLiteral("application/x-color"));
1351 }
1352 }
1353
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1354 bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1355 {
1356 // really check
1357 #if QT_CONFIG(draganddrop)
1358 return formatetc.tymed & TYMED_HGLOBAL
1359 && (formats.contains(formatetc.cfFormat)
1360 && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
1361 #else
1362 Q_UNUSED(mimeData);
1363 Q_UNUSED(formatetc);
1364 return formatetc.tymed & TYMED_HGLOBAL
1365 && formats.contains(formatetc.cfFormat);
1366 #endif // QT_CONFIG(draganddrop)
1367 }
1368
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1369 bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1370 {
1371 #if QT_CONFIG(draganddrop)
1372 return canConvertFromMime(formatetc, mimeData)
1373 && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
1374 #else
1375 Q_UNUSED(mimeData);
1376 Q_UNUSED(formatetc);
1377 Q_UNUSED(pmedium);
1378 return false;
1379 #endif // QT_CONFIG(draganddrop)
1380 }
1381
formatsForMime(const QString & mimeType,const QMimeData *) const1382 QVector<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
1383 {
1384 QVector<FORMATETC> formatetcs;
1385 auto mit = std::find(formats.begin(), formats.end(), mimeType);
1386 // register any other available formats
1387 if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
1388 mit = formats.insert(QWindowsMime::registerMimeType(mimeType), mimeType);
1389 if (mit != formats.end())
1390 formatetcs += setCf(mit.key());
1391
1392 if (!formatetcs.isEmpty())
1393 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1394 return formatetcs;
1395 }
1396 static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
1397
isCustomMimeType(const QString & mimeType)1398 static bool isCustomMimeType(const QString &mimeType)
1399 {
1400 return mimeType.startsWith(QLatin1String(x_qt_windows_mime), Qt::CaseInsensitive);
1401 }
1402
customMimeType(const QString & mimeType,int * lindex=nullptr)1403 static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
1404 {
1405 int len = sizeof(x_qt_windows_mime) - 1;
1406 int n = mimeType.lastIndexOf(u'\"') - len;
1407 QString ret = mimeType.mid(len, n);
1408
1409 const int beginPos = mimeType.indexOf(u";index=");
1410 if (beginPos > -1) {
1411 const int endPos = mimeType.indexOf(u';', beginPos + 1);
1412 const int indexStartPos = beginPos + 7;
1413 if (lindex)
1414 *lindex = mimeType.midRef(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
1415 } else {
1416 if (lindex)
1417 *lindex = -1;
1418 }
1419 return ret;
1420 }
1421
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1422 bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1423 {
1424 if (isCustomMimeType(mimeType)) {
1425 // MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
1426 QString clipFormat = customMimeType(mimeType);
1427 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1428 return canGetData(int(cf), pDataObj);
1429 }
1430 // if it is not in there then register it and see if we can get it
1431 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1432 const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType);
1433 return canGetData(cf, pDataObj);
1434 }
1435
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1436 QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1437 {
1438 Q_UNUSED(preferredType);
1439 QVariant val;
1440 if (canConvertToMime(mimeType, pDataObj)) {
1441 QByteArray data;
1442 if (isCustomMimeType(mimeType)) {
1443 int lindex;
1444 QString clipFormat = customMimeType(mimeType, &lindex);
1445 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1446 data = getData(int(cf), pDataObj, lindex);
1447 } else {
1448 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1449 const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType);
1450 data = getData(cf, pDataObj);
1451 }
1452 if (!data.isEmpty())
1453 val = data; // it should be enough to return the data and let QMimeData do the rest.
1454 }
1455 return val;
1456 }
1457
mimeForFormat(const FORMATETC & formatetc) const1458 QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
1459 {
1460 QString format = formats.value(getCf(formatetc));
1461 if (!format.isEmpty())
1462 return format;
1463
1464 const QString clipFormat = QWindowsMimeConverter::clipboardFormatName(getCf(formatetc));
1465 if (!clipFormat.isEmpty()) {
1466 #if QT_CONFIG(draganddrop)
1467 if (QInternalMimeData::canReadData(clipFormat))
1468 format = clipFormat;
1469 else if((formatetc.cfFormat >= 0xC000)){
1470 //create the mime as custom. not registered.
1471 if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
1472 //check if this is a mime type
1473 bool ianaType = false;
1474 int sz = ianaTypes.size();
1475 for (int i = 0; i < sz; i++) {
1476 if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
1477 ianaType = true;
1478 break;
1479 }
1480 }
1481 if (!ianaType)
1482 format = QLatin1String(x_qt_windows_mime) + clipFormat + u'"';
1483 else
1484 format = clipFormat;
1485 }
1486 }
1487 #endif // QT_CONFIG(draganddrop)
1488 }
1489
1490 return format;
1491 }
1492
1493 /*!
1494 \class QWindowsMimeConverter
1495 \brief Manages the list of QWindowsMime instances.
1496 \internal
1497 \sa QWindowsMime
1498 */
1499
1500 QWindowsMimeConverter::QWindowsMimeConverter() = default;
1501
~QWindowsMimeConverter()1502 QWindowsMimeConverter::~QWindowsMimeConverter()
1503 {
1504 qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
1505 }
1506
converterToMime(const QString & mimeType,IDataObject * pDataObj) const1507 QWindowsMime * QWindowsMimeConverter::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
1508 {
1509 ensureInitialized();
1510 for (int i = m_mimes.size()-1; i >= 0; --i) {
1511 if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
1512 return m_mimes.at(i);
1513 }
1514 return nullptr;
1515 }
1516
allMimesForFormats(IDataObject * pDataObj) const1517 QStringList QWindowsMimeConverter::allMimesForFormats(IDataObject *pDataObj) const
1518 {
1519 qCDebug(lcQpaMime) << "QWindowsMime::allMimesForFormats()";
1520 ensureInitialized();
1521 QStringList formats;
1522 LPENUMFORMATETC FAR fmtenum;
1523 HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
1524
1525 if (hr == NOERROR) {
1526 FORMATETC fmtetc;
1527 while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
1528 for (int i= m_mimes.size() - 1; i >= 0; --i) {
1529 QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
1530 if (!format.isEmpty() && !formats.contains(format)) {
1531 formats += format;
1532 if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
1533 qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
1534 }
1535 }
1536 // as documented in MSDN to avoid possible memleak
1537 if (fmtetc.ptd)
1538 CoTaskMemFree(fmtetc.ptd);
1539 }
1540 fmtenum->Release();
1541 }
1542 qCDebug(lcQpaMime) << pDataObj << formats;
1543 return formats;
1544 }
1545
converterFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1546 QWindowsMime * QWindowsMimeConverter::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1547 {
1548 ensureInitialized();
1549 qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
1550 for (int i = m_mimes.size()-1; i >= 0; --i) {
1551 if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
1552 return m_mimes.at(i);
1553 }
1554 return nullptr;
1555 }
1556
allFormatsForMime(const QMimeData * mimeData) const1557 QVector<FORMATETC> QWindowsMimeConverter::allFormatsForMime(const QMimeData *mimeData) const
1558 {
1559 ensureInitialized();
1560 QVector<FORMATETC> formatics;
1561 #if !QT_CONFIG(draganddrop)
1562 Q_UNUSED(mimeData);
1563 #else
1564 formatics.reserve(20);
1565 const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
1566 for (int f = 0; f < formats.size(); ++f) {
1567 for (int i = m_mimes.size() - 1; i >= 0; --i)
1568 formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
1569 }
1570 #endif // QT_CONFIG(draganddrop)
1571 return formatics;
1572 }
1573
ensureInitialized() const1574 void QWindowsMimeConverter::ensureInitialized() const
1575 {
1576 if (m_mimes.isEmpty()) {
1577 m_mimes
1578 #ifndef QT_NO_IMAGEFORMAT_BMP
1579 << new QWindowsMimeImage
1580 #endif //QT_NO_IMAGEFORMAT_BMP
1581 << new QLastResortMimes
1582 << new QWindowsMimeText << new QWindowsMimeURI
1583 << new QWindowsMimeHtml << new QBuiltInMimes;
1584 m_internalMimeCount = m_mimes.size();
1585 }
1586 }
1587
clipboardFormatName(int cf)1588 QString QWindowsMimeConverter::clipboardFormatName(int cf)
1589 {
1590 wchar_t buf[256] = {0};
1591 return GetClipboardFormatName(UINT(cf), buf, 255)
1592 ? QString::fromWCharArray(buf) : QString();
1593 }
1594
convertToMime(const QStringList & mimeTypes,IDataObject * pDataObj,QVariant::Type preferredType,QString * formatIn) const1595 QVariant QWindowsMimeConverter::convertToMime(const QStringList &mimeTypes,
1596 IDataObject *pDataObj,
1597 QVariant::Type preferredType,
1598 QString *formatIn /* = 0 */) const
1599 {
1600 for (const QString &format : mimeTypes) {
1601 if (const QWindowsMime *converter = converterToMime(format, pDataObj)) {
1602 if (converter->canConvertToMime(format, pDataObj)) {
1603 const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
1604 if (dataV.isValid()) {
1605 qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
1606 << format << pDataObj << " returns " << dataV;
1607 if (formatIn)
1608 *formatIn = format;
1609 return dataV;
1610 }
1611 }
1612 }
1613 }
1614 qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType;
1615 return QVariant();
1616 }
1617
registerMime(QWindowsMime * mime)1618 void QWindowsMimeConverter::registerMime(QWindowsMime *mime)
1619 {
1620 ensureInitialized();
1621 m_mimes.append(mime);
1622 }
1623
1624 QT_END_NAMESPACE
1625